個人開発

Astro + Cloudflare Workers でブログをゼロから構築した

ラップトップでコーディングしている様子
Photo by Negative Space on Pexels

訂正 (2026-08-09) — この記事の公開時のタイトルは「Astro + Cloudflare Pages でブログをゼロから構築した」でしたが、実際に使っていたのは Cloudflare Pages ではなく Workers の静的アセット (Static Assets) でした。タイトルと説明文を訂正しています。本文は当時のまま残し、該当箇所に注記を足しました。取り違えの経緯と両者の見分け方は「Cloudflare Pages でブログを作った」と書いていましたが、実体は Workers でした(2026-08-09 公開)に書いています。

なぜブログを作り直したのか

もともと techikoma.com で WordPress のブログをやっていたんですが、中身がだいぶ古くなっていて。ドメインだけ活かして、最近やっている個人開発の話を書ける場所が欲しくなりました。

旧コンテンツは全部破棄して完全に新規スタートです。技術の話だけじゃなく、いろいろ書いていくつもりです。

技術スタックの選定

選んだのはこの組み合わせです。

  • Astro 6 — 静的サイトジェネレータ。Content Collections で Markdown の記事管理が楽
  • Tailwind CSS v4 — CSS変数ベースのテーマ設定。@custom-variant でダークモード対応
  • Cloudflare Pages — ホスティング。GitHub 連携で push したら自動デプロイ

Astro を選んだ理由はシンプルで、ブログに必要な機能が標準で揃っているからですね。Content Collections で frontmatter のスキーマ定義ができて、RSS フィードも @astrojs/rss を入れるだけで済みます。自分は JS/TS メインの開発者なので、Hugo のテンプレート記法より Astro のほうが馴染みます。Next.js はブログにはちょっとオーバースペックですかね。

記事は Markdown で書いて Git 管理しています。ブラウザからも書けるように Decap CMS(旧 Netlify CMS)も入れてあります。

デザイン: Stitch でプロトタイプ

デザインは Google の Stitch を使いました。テキストでイメージを伝えると UI 画面を生成してくれるツールで、モバイル版と PC 版の画面をそれぞれ作成しています。

Stitch が出力したデザインシステムのポイントはこんな感じです。

  • Material Design 3 ベースのカラーシステム。エメラルドグリーン (#006947) をアクセントに
  • フォント: Plus Jakarta Sans(見出し)+ Manrope(本文)+ Noto Sans JP(日本語)
  • ボーダーを使わない: セクションの区切りは背景色のシフトで表現
  • グラスモーフィズム: ナビバーは半透明 + backdrop-blur

出力された HTML をそのまま使うのではなく、カラートークンやレイアウトパターンを抽出して Astro のコンポーネントに落とし込みました。

実装: Claude Code で一気に構築

実装は Claude Code で進めました。いい時代になったね。プロジェクト作成からデザイン適用、デプロイ設定まで 1 セッションでやっています。

主にやったことはこのあたりです。

  • Content Collections の定義src/content.config.ts で記事スキーマを定義。タイトル、説明、公開日、タグ、下書きフラグ
  • セマンティックデザイントークン--ds-bg--ds-text--ds-accent のような CSS 変数を :root.dark で切り替える仕組みです。コンポーネントでは [color:var(--ds-text)] のように書くだけで、ダークモードの分岐が不要になります
  • レイアウト構成 — BaseLayout → BlogPost / ページ。Header(グラスモーフィズムのナビ)と Footer は全ページ共通
  • RSS / タグページ@astrojs/rss でフィード生成、タグごとの記事一覧ページも自動生成

ちなみに Tailwind v4 ではクラスベースのダークモードを使うには @custom-variant dark (&:where(.dark, .dark *)); が必要でした。これがないと dark: プレフィックスが動かないんですよね。見落としやすいハマりポイントです。

ホスティング: なぜ Cloudflare Pages にしたか

注記 (2026-08-09) — この節を含め、以下で「Cloudflare Pages」と書いている箇所はすべて Workers の静的アセットの誤りです。AWS ではなく Cloudflare を選んだという判断そのものは変わりません。実際、下に貼っている wrangler.toml の例は [assets] で始まっていて、これは Workers の書き方です。詳しくは実体は Workers でした(2026-08-09 公開)を参照してください。

最初は AWS(S3 + CloudFront)で考えていました。ただコスト比較してみた結果、Cloudflare Pages に変更しています。

AWS (S3 + CloudFront)Cloudflare Pages
帯域1TB/月 無料 (Always Free)無制限
ストレージ5GB 無料 (12ヶ月限定)20,000ファイルまで
SSL無料 (ACM)無料
CI/CDGitHub Actions 自前構築ビルトイン
月額$0(Route 53 使わなければ)$0

AWS は「注意して運用すれば $0」で、Cloudflare Pages は「何も考えなくても $0」なんですよね。S3 のストレージ無料枠は 12ヶ月限定なので、長期運用だとその点も気にする必要があります。個人ブログなら Cloudflare のほうが楽です。

GitHub 連携するだけで push → 自動デプロイが完結するので、GitHub Actions のワークフローも不要になりました。

なお、2026年4月現在では Cloudflare Workers と Pages は統合が進んでいて、ダッシュボード上は同じ画面から設定します。デプロイ設定は wrangler.toml に数行書くだけです。

name = "my-blog"
compatibility_date = "2026-04-12"

[assets]
directory = "./dist"
not_found_handling = "404-page"
html_handling = "auto-trailing-slash"

ドメインの設定は、レジストラ側のネームサーバーを Cloudflare 指定のものに変更して、Cloudflare 側でカスタムドメインを追加するだけです。SSL 証明書も自動発行されます。

自動化: ブログネタ自動抽出 hook

Claude Code には hooks という機能があります。特定のイベント(ツール使用前後、セッション終了時など)にシェルスクリプトを実行できる仕組みです。

これを使って、Claude Code のセッションが終わるたびに「ブログネタになりそうな内容」を自動抽出する hook を作りました。

仕組みはシンプルで、こんな流れです。

  1. Stop hook でセッションの transcript を受け取る
  2. アシスタントの応答テキストを抽出(短いセッションはスキップ)
  3. バックグラウンドで claude -p に渡して分析
  4. ブログネタがあれば drafts ディレクトリに Markdown で保存

設定は ~/.claude/settings.json に追加するだけです。

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your-blog/scripts/extract-blog-idea.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

開発作業をしていれば勝手にネタが溜まっていきます。あとは drafts を眺めて、良さそうなものを記事に仕上げるだけですね。


参照リンク