独自ドメイン+Google Sites(Google APPS時代の)で運用していたこのサイト、相当前にGoogle Siteが使えなくなり長らくhttps://www.ixam.netのURLを放置していたがいい加減なんとか活用しようと思い、リニューアルすることにしたのがきっかけ。せっかくなのでサーバレスかつコンテンツがGoogle Siteの様な勝手に終了されるサービスに縛られない形にしたい、ということでChat GPT君とトライした成果。

正直Chat GPTだけで簡単にできるんじゃないかと思っていたが、なかなかどうして、基本的な要件を漏らしていたり、トラブルシュートが堂々巡りだったりとなかなか難しく、結局自前でググったGithubのリポジトリ「https://github.com/patrickgrey/website/」とかを参考にしつつ何とか共同作業で完成した感じ。

技術力はあるが勘違いや要件の認識間違いを頻繁に起こす部下を軌道修正しながら完成に導いている感強く、ある意味そのレベルまで来たAIがすごいなとも思った。 そして、内容が複雑に込み入ってくるとどんどんイラついて耐えられないレベルのアホさ加減を発揮してくる。 これもある意味リアルかも?

今のところは長々と会話を続けて前提条件としてすべてを理解しろという指示よりはある程度会話が込み入ってきたら人間が整理してまっさらなスレッドで改めて指示をし直すほうが、使える模様。 とはいえ私一人では調査・作業・勉強量的にも根気的にも明らかクリアできなかったと思うので、生成AIの生産性やばい。


ゴール

  • https://www.example.com/ でブログを表示できる
  • https://www.example.com/admin/ から DecapCMS にログインして記事を作成できる
  • 作成した記事は GitHub にコミットされ、自動で Cloudflare にデプロイされる
  • ちなみに現時点でランニングの追加費用は0(ドメイン登録料金はもともとかかっている。)

0. 前提とフォルダ構成

0-1. 前提

  • ドメイン(example.com)は取得済みとする

  • OS は Windows / macOS どちらでもよい(コマンド例は両方記載)

  • 使うサービス(無料枠でOK)

0-2. リポジトリの主な構成(例)

hugo.toml
content/
  blog/
  posts/
data/
functions/
  api/
    auth.js
    callback.js
layouts/
  _default/
    baseof.html
    index.html
    list.html
    single.html
static/
  _headers
  admin/
    config.yml
    index.html
  css/
    main.css

この構成は、以下の役割を持ちます:

  • hugo.toml … サイト全体の設定(baseURL は本番 URL に置換
  • functions/api/*.js … Cloudflare Pages Functions(GitHub OAuth 用 /api/auth/api/callback
  • layouts/_default/*.html … Hugo テンプレート
  • static/admin/DecapCMS の画面と設定
  • static/css/main.css … 見た目(CSS)
  • static/_headers … Cloudflare Pages の HTTP ヘッダー設定(任意)

※ この手順書では、**環境に応じて置換が必要な場所を「差し替えチェック」**として明示します。


1. 事前準備

1-1. GitHub アカウントを作る

  • ブラウザで GitHub にアクセスしてサインアップする

1-2. Cloudflare アカウントを作る

  • ブラウザで Cloudflare にアクセスしてサインアップする

1-3. Git と Hugo を入れる

  • Windows(PowerShell)

    PS C:\Users\alice> winget install Git.Git
    PS C:\Users\alice> winget install Hugo.Hugo.Extended
    
  • macOS(Terminal)

    mac:~ dev$ brew install git
    mac:~ dev$ brew install hugo
    

2. 手順本体(デプロイまで)

2-1. ローカルでリポジトリを用意する

  1. GitHub で空のリポジトリを作る

    • 画面:GitHub > New repository
    • 名前例:my-hugo-blog
  2. ローカルにクローンする

    • Windows

      PS C:\work> git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git
      PS C:\work> cd .\my-hugo-blog
      
    • macOS

      mac:~/work dev$ git clone https://github.com/<YOUR_GH_USERNAME>/my-hugo-blog.git
      mac:~/work dev$ cd my-hugo-blog
      
  3. 上記のフォルダとファイル一式をこのリポジトリ直下に配置する

差し替えチェック(ここは必ず直す)

  • hugo.toml

    baseURL = "https://www.example.com"   # ← 本番 URL に置換(例: https://www.example.com)
    languageCode = "ja-jp"
    title = "Example Blog"
    publishDir = "public"
    
    [permalinks]
      blog = "/blog/:year/:month/:slug/"
    
  • static/admin/config.yml

    backend:
      name: github
      repo: <YOUR_GH_USERNAME>/my-hugo-blog   # ← あなたの GitHub リポジトリに置換
      branch: master                          # ← リポジトリの既定ブランチ名(main か master)
      base_url: https://www.example.com       # ← 本番 URL に置換
      auth_endpoint: /api/auth                # ← Functions のエンドポイント(固定)
    media_folder: static/uploads
    public_folder: /uploads
    

参考:functions/api/auth.jsfunctions/api/callback.js はそのままで OK(url.origin を使うため、環境依存の直書きはありません)。

  1. 最初のコミット&プッシュ

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages"
      PS C:\work\my-hugo-blog> git push -u origin master
      
    • macOS

      mac:~/work/my-hugo-blog dev$ git add -A
      mac:~/work/my-hugo-blog dev$ git commit -m "Initial commit: Hugo + [DecapCMS](https://decapcms.org/) + CF Pages"
      mac:~/work/my-hugo-blog dev$ git push -u origin master
      

2-2. GitHub OAuth App を作る(DecapCMS ログイン用)

  1. 画面:GitHub > Settings > Developer settings > OAuth Apps > New OAuth App

  2. 入力:

    • Application name:[DecapCMS](https://decapcms.org/) for my-hugo-blog
    • Homepage URL:https://www.example.com
    • Authorization callback URL:https://www.example.com/api/callback重要
  3. 作成後に表示される:

    • Client ID
    • Client Secret(新規発行して控える)

これらは Cloudflare Pages の 環境変数に設定します。


2-3. Cloudflare Pages プロジェクトを作る

  1. 画面:Cloudflare ダッシュボード > Pages > Create a project > Connect to Git

  2. GitHub と連携し、リポジトリ my-hugo-blog を選ぶ

  3. ビルド設定:

    • Framework preset:None(または Cloudflare が自動検出)

    • Build command:hugo

    • Build output directory:public

    • 環境変数:

      • HUGO_VERSION = 0.128.0(例。ローカルの Hugo と合わせると安心)
      • GITHUB_CLIENT_ID = (前セクションで取得)
      • GITHUB_CLIENT_SECRET = (前セクションで取得)
  4. Save and Deploy を押して最初のデプロイを待つ

    • 成功すると *.pages.dev のプレビュー URL が発行される

2-4. 独自ドメイン www.example.com を割り当てる

  1. 画面:Cloudflare > Pages >(該当プロジェクト)> Custom domains > Set up a custom domain

  2. www.example.com を入力して追加する

  3. Cloudflare にドメインを 移管していない場合

    • Cloudflare の画面に表示される DNS レコード情報(例:CNAME www -> <project>.pages.dev)をメモする
    • ドメインを管理している レジストラの画面で、そのレコードを概念に従って設定する(ここは各レジストラの手順に従う)
  4. 反映後、https://www.example.com/ でサイトが開けることを確認する

ヒント:Cloudflare 側にドメインを追加している場合は、ボタンひとつで必要な DNS が自動設定されます。


2-5. DecapCMS 管理画面に入る

  1. ブラウザで https://www.example.com/admin/ を開く
  2. 「Login with GitHub」 のようなボタンから GitHub OAuth を許可する
  3. 初回は GitHub が「このアプリを承認しますか?」と聞くので Authorize を押す

ログインに失敗する場合は、OAuth の Callback URLCloudflare の環境変数GITHUB_CLIENT_ID/SECRET)を再確認します。


3. 運用手順(記事作成・ローカル同期・テンプレ編集)

3-1. CMS から記事を作成する

  1. 画面:https://www.example.com/admin/
  2. 左ナビ:BlogNew blog を押す
  3. 入力:TitlePublish DateDescriptionBody
  4. Publish を押すと、GitHub に自動コミット → Cloudflare が自動デプロイ

生成された Markdown は content/blog/ に配置されます。

3-2. ローカルリポジトリを最新にする(CMS で投稿後)

  • Windows

    PS C:\work\my-hugo-blog> git pull origin master
    
  • macOS

    mac:~/work/my-hugo-blog dev$ git pull origin master
    

こうしてローカルに最新の投稿ファイルを取り込み、テンプレートや CSS の編集を安全に進めます。

3-3. ローカルでデザインやテンプレートをいじる

  1. ローカルで開発サーバーを起動する

    • Windows

      PS C:\work\my-hugo-blog> hugo server -D
      
    • macOS

      mac:~/work/my-hugo-blog dev$ hugo server -D
      
    • ブラウザで http://localhost:1313/ を開いて確認

  2. 変更ポイント(例)

    • layouts/_default/baseof.html<head> やヘッダー/フッター
    • layouts/_default/index.html … トップの「最新記事」一覧
    • layouts/_default/single.html … 記事ページ本文
    • static/css/main.css … 色やフォント、余白など
  3. 変更をコミットしてプッシュ

    • Windows

      PS C:\work\my-hugo-blog> git add -A
      PS C:\work\my-hugo-blog> git commit -m "Update theme/layouts"
      PS C:\work\my-hugo-blog> git push
      
    • macOS

      mac:~/work/my-hugo-blog dev$ git add -A
      mac:~/work/my-hugo-blog dev$ git commit -m "Update theme/layouts"
      mac:~/work/my-hugo-blog dev$ git push
      
    • 数十秒~数分後、Cloudflare が自動デプロイ

3-4. ブランチ運用のワンポイント

  • 既定ブランチが mainmaster かを DecapCMSconfig.yml と Cloudflare の設定で揃える
  • プレビューしたい場合は、GitHub の Pull Request を作ると Cloudflare Pages が Preview 環境を自動発行

4. 便利な補足(任意)

4-1. static/_headers の例(管理画面はキャッシュしない)

/admin/*
  Cache-Control: no-store

4-2. robots とサイトマップ(必要に応じて)

  • static/robots.txt を用意してクローラ制御
  • hugo.tomloutputs を追加して RSS や sitemap の出力拡張も可能

5. よくあるつまずきと対処

  • CMS ログインが回るだけ
    → GitHub OAuth の Callback URLhttps://www.example.com/api/callback になっているか、Cloudflare 環境変数 GITHUB_CLIENT_ID/SECRET が正しいか確認
  • トップは出るのに記事ページが 404
    hugo.tomlbaseURL が実ドメインと一致しているか確認。Cloudflare のビルドログで public/ が生成されているかを見る
  • /admin が真っ白
    static/admin/index.htmlDecapCMS の読み込み(CDN)がブロックされていないか、ブラウザ拡張をオフにして再読込

6. 差し替え・設定チェック箇所 一覧(再掲)

ファイル/設定 キー 置換・設定内容(例)
hugo.toml baseURL https://www.example.com
static/admin/config.yml repo <YOUR_GH_USERNAME>/my-hugo-blog
static/admin/config.yml branch mainmaster を実態に合わせる
static/admin/config.yml base_url https://www.example.com
Cloudflare Pages HUGO_VERSION 例:0.128.0
Cloudflare Pages GITHUB_CLIENT_ID GitHub OAuth App の Client ID
Cloudflare Pages GITHUB_CLIENT_SECRET GitHub OAuth App の Client Secret
GitHub OAuth App Callback URL https://www.example.com/api/callback
Cloudflare Pages Custom domain www.example.com を追加

7. 付録:ファイルサンプル(必要な部分を置き換えて使用)

7-1. functions/api/auth.js

// Cloudflare Pages Functions(/api/auth)
export async function onRequest(context) {
  const { request, env } = context;
  const client_id = env.GITHUB_CLIENT_ID;
  try {
    const url = new URL(request.url);
    const redirectUrl = new URL('https://github.com/login/oauth/authorize');
    redirectUrl.searchParams.set('client_id', client_id);
    redirectUrl.searchParams.set('redirect_uri', `${url.origin}/api/callback`);
    redirectUrl.searchParams.set('scope', 'repo user');
    redirectUrl.searchParams.set('state', crypto.getRandomValues(new Uint8Array(12)).join(''));
    return Response.redirect(redirectUrl.href, 302);
  } catch (err) {
    return new Response(String(err?.message || err), { status: 500 });
  }
}

7-2. functions/api/callback.js

function renderBody(status, content) {
  const html = `
  <script>
    const receiveMessage = (message) => {
      window.opener.postMessage(
        'authorization:github:${status}:${JSON.stringify(content)}',
        message.origin
      );
      window.removeEventListener("message", receiveMessage, false);
    }
    window.addEventListener("message", receiveMessage, false);
    window.opener.postMessage("authorizing:github", "*");
  </script>`;
  return new Blob([html]);
}

export async function onRequest(context) {
  const { request, env } = context;
  const client_id = env.GITHUB_CLIENT_ID;
  const client_secret = env.GITHUB_CLIENT_SECRET;
  try {
    const url = new URL(request.url);
    const code = url.searchParams.get('code');
    const response = await fetch('https://github.com/login/oauth/access_token', {
      method: 'POST',
      headers: { 'content-type': 'application/json', 'user-agent': 'cf-pages-oauth', 'accept': 'application/json' },
      body: JSON.stringify({ client_id, client_secret, code }),
    });
    const result = await response.json();
    if (result.error) {
      return new Response(renderBody('error', result), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 401 });
    }
    const token = result.access_token;
    const provider = 'github';
    return new Response(renderBody('success', { token, provider }), { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 200 });
  } catch (error) {
    return new Response(error.message, { headers: { 'content-type': 'text/html;charset=UTF-8' }, status: 500 });
  }
}

7-3. static/admin/config.yml

backend:
  name: github
  repo: <YOUR_GH_USERNAME>/my-hugo-blog
  branch: main
  base_url: https://www.example.com
  auth_endpoint: /api/auth

media_folder: static/uploads
public_folder: /uploads

collections:
  - name: 'blog'
    label: 'Blog'
    folder: 'content/blog'
    create: true
    slug: '{{slug}}'
    editor:
      preview: false
    fields:
      - { label: 'Title', name: 'title', widget: 'string' }
      - { label: 'Publish Date', name: 'date', widget: 'datetime' }
      - { label: 'Description', name: 'description', widget: 'string' }
      - { label: 'Body', name: 'body', widget: 'markdown' }


9. SEO & パフォーマンス最適化のポイント

ブログの構築後は、以下の施策を行うことで検索順位や表示速度をさらに改善できます。

  1. サイトマップとrobots.txtの設定

    • Hugoで自動生成されるpublic/sitemap.xmlをGoogle Search Consoleに登録
    • static/robots.txtにクローラ制御を追加
  2. OGP(Open Graph Protocol)とTwitterカード設定

    • layouts/_default/baseof.html<meta property="og:title"><meta property="og:image">を追加
    • SNSでのシェア時に魅力的に見せる
  3. 画像のWebP変換

    • resources/_gen/images/ を利用し、Hugoのパイプラインで自動WebP化
    • ページ読み込み速度を向上
  4. カテゴリ・タグ機能の追加

    • content/blog/のFront Matterにcategoriestagsを追加し、テンプレートでタグクラウドを表示
  5. 構造化データの導入

    • JSON-LD形式で記事情報を検索エンジンに明示し、リッチリザルトを狙う

8. まとめ

  • GitHub にリポジトリ、Cloudflare Pages に接続、DecapCMS 用の GitHub OAuth を設定し、独自ドメインを割り当てれば完成
  • 記事は /admin/ から作成 → GitHub にコミット → Cloudflare が自動デプロイ
  • テンプレやデザインはローカルで hugo server しながら編集して、Git で反映

これで、Hugo + GitHub + Decap CMS + Cloudflare のサーバレスブログ環境が構築・運用可能になります。

さて、これで一通り維持費の超軽量なサイト構築はできたが、サイト内検索、カテゴリ、タグ&タグクラウド等々、既存のブログサイト等と比べると見劣り感が強いのは、いったんやむなしかなぁと。徐々にChatGPTと二人三脚で拡充していきたい。