Cloudflare Workersを個人開発CMSのAPIに使った構成と実装例 Cloudflare

Cloudflare Workersを個人開発CMSのAPIに使った構成と実装例

Cloudflare

Cloudflare WorkersでCMSのHTML、API、RSS、sitemap、Assets、Cronをまとめて扱いたい人向けに、実際のルーティングを整理します。

Cloudflare WorkersでCMSのHTMLとAPIを返す構成図
fetch handlerで公開ページ、管理API、RSS、sitemap、Assetsを振り分けています。

この記事は、Cloudflare Workersで小さなCMSや管理画面付きサイトを作りたい人向けです。静的なページだけではなく、D1から記事を読み、管理APIを受け、RSSとsitemapを動的に返す構成をどう組むかを、このサイトの src/index.ts に沿って説明します。

Cloudflare Workersとは何か、という一般論ではなく、このCMSでは何をWorkersに任せているのかを見ます。

解決したかった問題

最初は、公開ページだけなら静的サイトでもよく見えます。しかし、このサイトには管理画面、記事投稿API、カテゴリ管理、問い合わせ保存、XML-RPC投稿、予約投稿、RSS、sitemap、robots.txt、静的アセット配信があります。これらを別々のサービスに分けると、個人開発としては運用が重くなります。

そこで、Workerを小さなバックエンドとして使い、D1とAssetsを同じプロジェクトから扱う構成にしています。

技術記事だけを表示している公開記事一覧のスクリーンショット
AdSenseモード中の公開記事一覧では、技術記事、事例、作業記録だけを表示します。

wrangler.tomlの構成

main = "src/index.ts"

[[d1_databases]]
binding = "DB"
database_name = "cms-db"

[assets]
directory = "./public"
binding = "ASSETS"

Worker本体は src/index.ts、DBは env.DB、静的ファイルは env.ASSETS で扱います。記事画像やOGP画像は public/ 配下に置き、/assets/... のURLで返します。

リクエストの振り分け

if (pathname === "/robots.txt") return robotsTxt(request, env);
if (pathname.startsWith("/api/")) return handleApi(request, env, pathname);
if (pathname === "/xmlrpc.php") return handleXmlRpc(request, env);
if (pathname === "/rss.xml") return renderRss(request, env);
if (pathname === "/sitemap.xml") return renderSitemap(request, env);
if (pathname.startsWith("/admin")) return handleAdminPage(pathname);
return handlePublicPage(request, env, pathname, ctx);

フレームワークを増やさず、明示的にURLを振り分けています。規模が大きくなればルーターを入れる選択もありますが、このCMSでは処理の入口を1ファイルで追えることを優先しました。

APIと認証

管理画面のAPIはBasic認証または管理パスワードで保護します。GASからのインポートAPIはBearer Tokenで守ります。公開コメントAPIはありますが、審査中は ENABLE_PUBLIC_COMMENTS=false で閉じています。

この判断は、機能を削るためではなく、公開面に出すものと管理者だけが使うものを分けるためです。

RSSとsitemapをWorkerで作る理由

RSSとsitemapは固定ファイルではありません。D1の公開記事を読み、下書き、noindex、未レビュー、審査対象外の記事タイプを除外して出力します。記事一覧、検索、RSS、sitemapが同じ公開条件を使うようにしたことで、余計なURLが検索エンジンへ出にくくなります。

失敗しやすい判断

  • 公開ページだけを見て静的サイトで十分と判断する
  • RSSやsitemapだけ別条件で作る
  • 管理APIと公開APIの認証を混ぜる
  • 画像をD1に入れてしまい、配信とDBの役割が曖昧になる
  • 旧URLの扱いを決めずにslugを変える

このサイトでは、HTMLとAPIはWorker、記事状態はD1、画像はAssetsと分けました。すべてを1つに詰め込むのではなく、Workerを入口として役割を分けるのが扱いやすい判断でした。

同じ構成を作る手順

  1. wrangler.toml にWorker、D1、Assetsを設定する
  2. 公開ページと管理APIをURLで分ける
  3. 公開記事の取得条件を関数化する
  4. RSSとsitemapも同じ条件を使う
  5. ローカルで pnpm testpnpm typecheck を通す
  6. 本番はDB migrationを先に当て、その後Workerをdeployする

特に最後の順番は重要です。Workerコードが新しいDB列を読む場合、コードだけ先に出すと本番でエラーになります。このCMSでも review_status を追加したため、migrationを先に流す必要があります。

参考資料