Cloudflare D1で記事の下書き・予約投稿・公開状態を管理する方法 Cloudflare

Cloudflare D1で記事の下書き・予約投稿・公開状態を管理する方法

Cloudflare

D1で小さなCMSを作る人向けに、postsテーブル、公開条件、予約投稿、review_statusを実際のSQLとWorkerコードで整理します。

Cloudflare D1で記事状態を管理する図解
postsテーブルで、下書き、予約、公開、noindex、レビュー状態を分けて管理します。

この記事は、Cloudflare D1で個人開発向けCMSを作りたい人に向けて書いています。特に、記事をDBに入れるだけでなく、下書き、予約投稿、公開済み、noindex、レビュー待ちをどう分けるかで迷っている人のための実装メモです。

このサイトでは、記事本文だけでなく公開制御もD1に寄せています。単なるD1紹介ではなく、実際のmigrationとWorkerコードをもとに、公開される記事をどう絞っているかを整理します。

Cloudflare D1の記事が公開画面で表示されているスクリーンショット
公開画面では、記事本文、カテゴリ、関連記事、最近の記事が同じWorkerから表示されます。

解決したかった問題

最初に困るのは、管理画面では見たい記事と、読者に公開してよい記事が一致しないことです。下書き、予約中、公開済み、削除済み、検索に出したくない記事、事実確認が残る記事を同じ一覧に混ぜると、RSSやsitemapに余計なURLが入りやすくなります。

そのため、このCMSでは記事を削除して整理するのではなく、DBの列で状態を分けます。審査中に公開しない記事を隠す場合も、データを消すのではなく公開条件から外します。

postsテーブルに持たせた状態

初期のpostsテーブルには、タイトル、slug、本文HTML、ステータス、予約日時、公開日時がありました。その後、AdSense審査用モードへ切り替えるために、抜粋、サムネイル、noindex、content_type、review_statusを追加しています。

ALTER TABLE posts ADD COLUMN review_status TEXT NOT NULL DEFAULT reviewed;

status: draft / scheduled / published / private / deleted
content_type: technical / case_study / worklog / legacy
review_status: reviewed / draft_review / fact_check_required

ここで重要なのは、statusreview_status を分けたことです。記事としては公開済みでも、本文に確認待ちが残っているなら fact_check_required にできます。noindexとは別の、編集上の安全弁です。

Worker側の公開条件

公開一覧、検索、RSS、sitemapでは同じ条件を使います。実装上は src/index.tspublicPublishedPostConditions に寄せています。

function publicPublishedPostConditions(env) {
  const contentTypes = publicContentTypes(env);
  return [
    "status = published",
    "deleted_at IS NULL",
    "COALESCE(noindex, 0) = 0",
    "COALESCE(review_status, reviewed) = reviewed",
    "COALESCE(content_type, legacy) IN (...)"
  ];
}

記事一覧だけで条件を変えると、記事詳細では見えるのにsitemapへ出ない、RSSには出るのに検索では出ない、というズレが起きます。ここは最初から共通化しておく方が後で楽でした。

予約投稿の流れ

  1. 管理画面で記事を作り、ステータスを scheduled にする
  2. scheduled_at に投稿予定時刻を入れる
  3. Cloudflare Cronが定期的にWorkerを起動する
  4. publishDuePosts が予定時刻を過ぎた記事を published に変える
  5. published_at を入れ、一覧やRSSの並び順に使う

この方式にしておくと、管理画面から手動公開しても、予約公開しても、最終的に公開条件は同じになります。

失敗しやすい判断

一番危ないのは、公開したくない記事を noindex だけで管理することです。noindexは検索エンジン向けの指示であって、サイト内一覧やRSSから消す条件ではありません。このCMSでは、noindex記事をRSSとsitemapからも除外するようにしています。

もうひとつは、AdSense審査中の記事タイプを blog のような広い分類で出すことです。今回は technicalcase_studyworklog だけを公開対象にし、審査対象外の記事タイプは外しました。

同じ構成を作る手順

  1. postsテーブルに statusnoindexcontent_typereview_status を持たせる
  2. 公開記事を取得するSQL条件を1か所にまとめる
  3. RSSとsitemapも同じ条件を使う
  4. 管理画面からcontent_typeとreview_statusを変更できるようにする
  5. 未確認記事は削除せず fact_check_required で止める

D1は、こうした小さなCMSの状態管理に向いています。ただし、画像、動画、大量のアクセスログまで全部D1に寄せる必要はありません。このサイトでは、記事本文と公開制御をD1、画像はWorkers Assetsという役割分担にしています。

参考資料