この記事には広告・アフィリエイトリンクを含む場合があります。
Tech/Cloudflare

WordPressなしでブログ作ってみた!Cloudflare Pages + Workers + D1 + microCMS 構成のすべて

WordPressじゃなくていいじゃん、と思って自分で構成を考えてブログを作ってみました。ほぼ無料で動いてるし、めちゃくちゃ快適なのでまとめておきます!


そもそもなんでWordPressを使わなかったの?

ブログを作ろうと思ったとき、最初は「とりあえずWordPress?」ってなりますよね。でも、ちょっと待って、と思いました。

WordPressをやめた理由

  • サーバー管理したくない:VPSを借りてセットアップして、アップデートして……個人ブログにそこまでやりたくなかった
  • 地味にお金がかかる:まともに運用すると月1,000〜2,000円はかかる
  • プラグイン増やすと遅くなる:表示速度が気になってた
  • エンジニアなら自分で作りたい!:せっかくなので好き勝手やってみたかった

というわけで、Cloudflareのエコシステムをフル活用した静的サイト + エッジ処理構成でやってみました!


全体の構成はこんな感じ

まず完成形の全体像を見てもらいましょう。

[ブラウザ]
    │
    ▼
[Cloudflare Pages] ─── 静的HTML/CSS/JSのホスティング
    │
    ├── [Cloudflare Workers] ─── APIエンドポイント(記事取得・フォーム処理など)
    │       │
    │       └── [Cloudflare D1] ─── SQLiteベースのエッジDB(コメント等)
    │
    └── [microCMS] ─── ヘッドレスCMS(記事の執筆・管理)
            │
            └── Webhook ──▶ [GitHub Actions] ──▶ 再ビルド&デプロイ

レイヤー

サービス

役割

ホスティング

Cloudflare Pages

静的サイトのビルド&配信

バックエンド

Cloudflare Workers

APIロジック

データベース

Cloudflare D1

コメント・設定の永続化

CMS

microCMS

記事の作成・管理UI

CI/CD

GitHub Actions

自動ビルド&デプロイ

アクセス制限

Cloudflare Zero Trust Access

管理画面の認証

アクセス解析

Cloudflare Web Analytics

プライバシーフレンドリーな解析

全部Cloudflareでまとめられるのが気持ちいいポイントです。


実際にやってみた手順

Step 1:Cloudflareアカウント作成とプロジェクト初期化

まず dash.cloudflare.com でアカウントを作ります。無料プランで全然OKです。

Pagesプロジェクトはこれだけ!

npm install -g wrangler
wrangler login
wrangler pages project create my-blog

フレームワークはAstroを選びました。静的サイト生成との相性が抜群で、Cloudflareとの公式インテグレーションもバッチリ整っています。

npm create astro@latest my-blog
cd my-blog
npx astro add cloudflare

Step 2:Cloudflare D1でデータベースを作成してみた

D1はCloudflareが提供するSQLiteベースのエッジデータベースです。Workersから直接アクセスできてめちゃくちゃ便利!

# D1データベースを作成
wrangler d1 create my-blog-db

# wrangler.tomlに追記(コマンド実行後に出力される内容をコピーするだけ)

wrangler.toml はこんな感じ:

[[d1_databases]]
binding = "DB"
database_name = "my-blog-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

スキーマを作ってマイグレーションも一発です:

-- schema.sql
CREATE TABLE IF NOT EXISTS comments (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  post_slug TEXT NOT NULL,
  author TEXT NOT NULL,
  body TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
wrangler d1 execute my-blog-db --file ./schema.sql

Step 3:Workers でAPIを作ってみた

コメント投稿・取得のAPIをWorkerとして実装しました。Pages Functionsを使うと、functions/ ディレクトリにファイルを置くだけで自動的にルーティングされます。これは便利すぎる。

// functions/api/comments/[slug].ts
export const onRequestGet: PagesFunction<Env> = async (ctx) => {
  const slug = ctx.params.slug as string;
  const { results } = await ctx.env.DB.prepare(
    "SELECT * FROM comments WHERE post_slug = ? ORDER BY created_at DESC"
  ).bind(slug).all();

  return Response.json(results);
};

export const onRequestPost: PagesFunction<Env> = async (ctx) => {
  const slug = ctx.params.slug as string;
  const { author, body } = await ctx.request.json<{ author: string; body: string }>();

  await ctx.env.DB.prepare(
    "INSERT INTO comments (post_slug, author, body) VALUES (?, ?, ?)"
  ).bind(slug, author, body).run();

  return Response.json({ success: true }, { status: 201 });
};

Step 4:GitHubと連携して自動デプロイ

CloudflareのPagesはGitHubリポジトリと連携できます。設定は3ステップだけ!

  1. GitHubにリポジトリをプッシュ
  2. Cloudflareダッシュボード → Pages → 「Connect to Git」
  3. ビルドコマンドに npm run build、出力ディレクトリに dist を設定
git push origin main
# → 自動でビルド&デプロイが走る!気持ちいい

Step 5:独自ドメインも簡単に設定できた

Pagesのダッシュボードから「Custom domains」→「Set up a custom domain」。ネームサーバーをCloudflareに向けてあれば、数クリックで完了します。HTTPS証明書も自動で発行されるのが最高すぎる。


microCMSをヘッドレスCMSとして使ってみた

最初はMarkdownファイルで記事を管理しようとしてたんですが、スマホから下書きを書きたいときに不便すぎて断念。そこで microCMS を入れてみました。

microCMSの設定手順

  1. microCMSでサービスを作成
  2. APIスキーマを定義(タイトル、本文、カバー画像、タグなど)
  3. APIキーを取得して環境変数に設定
# Cloudflare Pagesの環境変数として設定
wrangler pages secret put MICROCMS_API_KEY
wrangler pages secret put MICROCMS_SERVICE_DOMAIN

AstroからmicroCMSを呼び出す

// src/lib/microcms.ts
import { createClient } from "microcms-js-sdk";

export const client = createClient({
  serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: import.meta.env.MICROCMS_API_KEY,
});

export const getPosts = async () => {
  return await client.getList({ endpoint: "posts" });
};

記事ページは静的生成(SSG)で出力します:

---
// src/pages/posts/[id].astro
import { client } from "../../lib/microcms";

export const getStaticPaths = async () => {
  const { contents } = await client.getList({ endpoint: "posts", queries: { limit: 100 } });
  return contents.map((post) => ({ params: { id: post.id }, props: { post } }));
};

const { post } = Astro.props;
---

<h1>{post.title}</h1>
<div set:html={post.body} />

microCMS Webhook + GitHub Actions で自動デプロイも実現!

microCMSで記事を公開したら、自動でサイトを再ビルド&デプロイしたいですよね。WebhookとGitHub Actionsを組み合わせたら実現できました!

GitHub Actionsの設定

# .github/workflows/deploy.yml
name: Deploy on microCMS Update

on:
  repository_dispatch:
    types: [microcms_update]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build
        env:
          MICROCMS_API_KEY: ${{ secrets.MICROCMS_API_KEY }}
          MICROCMS_SERVICE_DOMAIN: ${{ secrets.MICROCMS_SERVICE_DOMAIN }}

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: my-blog
          directory: dist

GitHub Actionsを起動するWebhook受信用Workerも作った

microCMSのWebhookはURLに直接POSTを飛ばすので、GitHub Actionsを起動するための中継Workerを用意しました。

// functions/api/webhook.ts
export const onRequestPost: PagesFunction<Env> = async (ctx) => {
  const secret = ctx.request.headers.get("X-MICROCMS-Signature");
  // シグネチャ検証(省略)

  await fetch(
    `https://api.github.com/repos/${ctx.env.GITHUB_REPO}/dispatches`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${ctx.env.GITHUB_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ event_type: "microcms_update" }),
    }
  );

  return new Response("OK");
};

Zero Trust Accessで管理画面をガードしてみた

管理用のパスは自分しかアクセスできないようにしたかったんですが、Cloudflare Zero Trust Accessを使ったらコードを一行も書かずに実現できました!

設定手順

  1. Cloudflareダッシュボード → 「Zero Trust」→「Access」→「Applications」
  2. 「Add an application」→「Self-hosted」
  3. 対象URL(例:https://myblog.com/admin/*)を設定
  4. Policyで許可するメールアドレスを指定
Policy Type: Allow
Rule: Emails → [your@email.com]

これだけで、管理画面にアクセスするとCloudflareのログイン画面が挟まって、指定したメールアドレスにワンタイムコードが飛んできます。Google OAuthとの連携もできます。セキュリティがタダで手に入る感じ、最高です。


ハマりポイント:Bot Fight ModeがWebhookをブロックしてた!

これが一番詰まりました。microCMSのWebhookを設定して記事を公開しても、GitHub Actionsがいっこうに起動しない。Webhookの送信ログを見ると、ステータスコードが 403 になってる。

最初はWorker側のコードを疑って色々見直したんですが、ローカルで同じリクエストを投げるとちゃんと動く。なんで……?

原因はBot Fight Modeだった

Cloudflareの Bot Fight Mode が有効になっていて、microCMSのサーバーからのリクエストをボットと判定してブロックしていたのが原因でした。

Bot Fight Mode は、既知のボットのIPアドレスからのリクエストをチャレンジまたはブロックする機能です。

microCMS側のIPが怪しいと思われてしまったわけです。まさかそこが原因とは……という感じでした。

解決策:WebhookのパスだけBotチェックをスキップ

WAFカスタムルールでWebhookのエンドポイントをBot Fight Modeの対象外にしました。

Cloudflareダッシュボード → Security → WAF → Custom rules から以下のルールを追加:

Field: URI Path
Operator: equals
Value: /api/webhook

Action: Skip → Bot Fight Mode

Bot Fight Modeを全体で無効にするんじゃなくて、Webhookのパスだけスキップするのがポイントです。これで無事に動きました!


Cloudflare Web Analyticsでアクセス解析も入れてみた

Google Analyticsは使いたくなかったんです。クッキーバナーが必要になるし、ユーザーの行動を過剰に追跡する仕組みには加担したくないな、と。

Cloudflare Web Analyticsはクッキー不使用、IPアドレスも収集しないのでプライバシーフレンドリー。GDPR対応もしやすいです。

設定はスクリプト1行だけ

Cloudflareダッシュボード → Analytics & Logs → Web Analytics → 「Add a site」

発行されるスクリプトをHTMLに埋め込むだけ:

<script defer src='https://static.cloudflareinsights.com/beacon.min.js'
  data-cf-beacon='{"token": "your-token-here"}'></script>

Astroなら src/layouts/BaseLayout.astro<head> に追記するだけ。5分で終わります。

見られる指標はこんな感じ:

  • ページビュー数・ユニーク訪問者数
  • 参照元(リファラー)
  • 国・デバイス別の内訳
  • 上位ページ

Google Analyticsほど細かくはないですが、個人ブログには十分すぎます。


まとめ:費用と感想

費用の内訳

サービス

プラン

月額費用

Cloudflare Pages

Free

¥0

Cloudflare Workers

Free(10万リクエスト/日まで)

¥0

Cloudflare D1

Free(5GB・500万読み取り/日まで)

¥0

Cloudflare Zero Trust Access

Free(50ユーザーまで)

¥0

Cloudflare Web Analytics

Free

¥0

microCMS

Hobby(3API・3コンテンツまで)

¥0

ドメイン(.com)

-

約¥150/月

合計

約¥150/月

ドメイン代だけ!実質無料です。

やってみてよかったこと

  • サーバー管理が一切不要:Cloudflareが全部面倒を見てくれる
  • スケール不安ゼロ:エッジで動くのでバズっても平気(たぶん)
  • 表示が速い:静的サイト + CDNの組み合わせは本当に強い
  • セキュリティが堅い:Zero Trust Access、Bot Fight Mode、WAFが全部入ってる

大変だったこと

  • Bot Fight Modeのハマり:原因の特定に数時間かかりました(つらかった)
  • microCMS + Astroの型定義:SDKの型と実際のレスポンスがずれていてデバッグが辛かった
  • D1のローカル開発環境wrangler dev でのD1ローカルエミュレーションがたまに挙動が違う

こんな人におすすめ!

向いてる人

  • サーバーレス・エッジコンピューティングに興味がある
  • コストをできるだけ下げたい
  • 記事数が数百件以内の個人ブログ

向いてない人

  • プラグインで気軽に機能追加したい
  • 非エンジニアと共同管理したい
  • WordPressで慣れてる

Cloudflareのエコシステム、思っていた以上に完成されてました。「え、これ無料でできるの?」の連続でした。サーバーレス・エッジ構成に興味があるエンジニアなら、個人ブログをその実験場にするのめちゃくちゃおすすめです。

最初はハードル高そうに見えるかもですが、一度動かしてしまえばメンテナンスはほぼゼロ。あとは記事を書くだけの環境が手に入ります!