
この記事は、Cloudflare Workersで小さなCMSや管理画面付きサイトを作りたい人向けです。静的なページだけではなく、D1から記事を読み、管理APIを受け、RSSとsitemapを動的に返す構成をどう組むかを、このサイトの src/index.ts に沿って説明します。
Cloudflare Workersとは何か、という一般論ではなく、このCMSでは何をWorkersに任せているのかを見ます。
解決したかった問題
最初は、公開ページだけなら静的サイトでもよく見えます。しかし、このサイトには管理画面、記事投稿API、カテゴリ管理、問い合わせ保存、XML-RPC投稿、予約投稿、RSS、sitemap、robots.txt、静的アセット配信があります。これらを別々のサービスに分けると、個人開発としては運用が重くなります。
そこで、Workerを小さなバックエンドとして使い、D1とAssetsを同じプロジェクトから扱う構成にしています。

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を入口として役割を分けるのが扱いやすい判断でした。
同じ構成を作る手順
wrangler.tomlにWorker、D1、Assetsを設定する- 公開ページと管理APIをURLで分ける
- 公開記事の取得条件を関数化する
- RSSとsitemapも同じ条件を使う
- ローカルで
pnpm testとpnpm typecheckを通す - 本番はDB migrationを先に当て、その後Workerをdeployする
特に最後の順番は重要です。Workerコードが新しいDB列を読む場合、コードだけ先に出すと本番でエラーになります。このCMSでも review_status を追加したため、migrationを先に流す必要があります。