Cloudflareへのデプロイ

このページ

Cloudflare Workersは、EmDash向けの高速でグローバルに分散されたランタイムを提供します。このガイドでは、データベースにD1、メディアストレージにR2を使用したデプロイについて説明します。

前提条件

  • Cloudflareアカウント
  • Wrangler CLIがインストール済み(npm install -g wrangler
  • Cloudflareで認証済み(wrangler login

バインディングの設定

プロジェクトルートにD1とR2のバインディングを含む wrangler.jsonc を作成します。Wranglerは、まだ存在しない場合、最初のデプロイ時に両方のリソースをプロビジョニングします。

{
	"$schema": "node_modules/wrangler/config-schema.json",
	"name": "my-emdash-site",
	"compatibility_date": "2025-01-15",
	"compatibility_flags": ["nodejs_compat"],

	"d1_databases": [
		{
			"binding": "DB",
			"database_name": "emdash-db",
		},
	],

	"r2_buckets": [
		{
			"binding": "MEDIA",
			"bucket_name": "emdash-media",
		},
	],
}

EmDashの設定

Astro設定をD1とR2を使用するように更新します:

import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import react from "@astrojs/react";
import emdash from "emdash/astro";
import { d1, r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	output: "server",
	adapter: cloudflare(),
	integrations: [
		react(), // 必須 — 管理UIはReactアプリ
		emdash({
			database: d1({ binding: "DB" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

初回起動

データベースマイグレーションはデプロイ後の最初のリクエスト時に自動的に実行され、新しく適用するものがあれば以降の起動時にも実行されます。

データベースが空(コレクションなし)でセットアップウィザードが完了していない場合、EmDashは初回起動時にシードファイルも適用します。シードはビルド時に .emdash/seed.jsonpackage.json#emdash.seed のパス、または seed/seed.json から読み取られ — 最初に見つかったものが使用され — バンドルにインライン化されます。いずれも存在しない場合、組み込みのデフォルトシードが使用されます。既存のデータベースへの後続のデプロイはコンテンツをそのまま残します。

すでにデプロイされたサイトのスキーマやコンテンツモデルを変更するには、デプロイされたサイトの進化を参照してください。

スケジュール公開

Cloudflare Workersでは、スケジュール公開、プラグインcron、メンテナンスタスクはWorker Cron Triggerから実行されます。新しいCloudflareテンプレートにはこのセットアップが自動的に含まれています。既存のプロジェクトを更新する場合は、@emdash-cms/cloudflare/worker からEmDash Workerエントリをエクスポートします:

export { default, PluginBridge } from "@emdash-cms/cloudflare/worker";

次に wrangler.jsonc にCron Triggerを追加します:

{
	"triggers": {
		"crons": ["* * * * *"],
	},
}

デプロイ

Cloudflare Workersにデプロイ:

wrangler deploy

サイトは https://my-emdash-site.<your-subdomain>.workers.dev でライブになりました。

Read Replica

グローバルに分散されたサイトの場合、D1のリードレプリケーションを有効にして、常にプライマリデータベースにアクセスするのではなく、近くのレプリカに読み取りクエリをルーティングします。これにより、プライマリリージョンから離れた訪問者のレイテンシーが大幅に削減されます。

emdash({
	database: d1({
		binding: "DB",
		session: "auto",
	}),
	storage: r2({ binding: "MEDIA" }),
}),

Cloudflareダッシュボードまたは REST APIで、D1データベース自体のリードレプリケーションも有効にする必要があります。

セッションモードとブックマークベースの整合性の仕組みについては、データベースオプション — Read Replicaを参照してください。

Object Cache

D1の読み取り負荷を軽減するには、コンテンツと設定のクエリ結果をCloudflare KVにキャッシュします。リクエストごとにデータベースをクエリする代わりに、KVから読み取りが提供されます:

import { d1, r2, kvCache } from "@emdash-cms/cloudflare";

emdash({
	database: d1({ binding: "DB" }),
	storage: r2({ binding: "MEDIA" }),
	objectCache: kvCache({ binding: "CACHE" }),
}),

KVのセットアップ、オプション、無効化の動作については、Object Cacheを参照してください。

Workers Cache

CloudflareのWorkers Cachewrangler.jsonc"cache": { "enabled": true })は、Worker の前に エッジキャッシュを配置します:一致するリクエストはWorkerを実行せずに提供されます。これはEmDashとうまく機能します:

  • EmDashの管理とAPIレスポンスは Cache-Control: private, no-store を送信し、保存されることはありません。
  • パブリックページは、返す Cache-Control ヘッダーを通じて独自のキャッシュを制御します。

有効化前に知っておくべき2つのこと:

  1. Cache-Control ヘッダーのないレスポンスもキャッシュされます。 Workers CacheはRFC 9111のヒューリスティックフレッシュネスを適用します — ヘッダーなしの 200 は2時間キャッシュされます。すべてのカスタムルートに明示的な Cache-Control を指定してください(セッション依存のものには private, no-store を使用)。
  2. キャッシュされたページはログイン中のエディターと共有されます。 キャッシュはWorkerの前に実行されるため、リクエストCookieに基づいてバイパスできません。ログイン中のエディターは、エントリが期限切れになるまで、パブリックページのキャッシュされた匿名バリアント — ビジュアル編集ツールバーなし — を受け取る可能性があります。エディターがレンダリングしたレスポンス自体は保存されることはなく(private, no-store を持つ)、逆方向に漏洩することはありません。

カスタムドメイン

Cloudflareダッシュボードでカスタムドメインを追加します:

  1. Workers & Pages > あなたのworkerに移動
  2. Custom Domains > Add Custom Domain をクリック
  3. ドメインを入力し、DNSセットアップの手順に従います

パブリックR2アクセス

R2からメディアを直接配信するには(パフォーマンスのため推奨):

  1. Cloudflareダッシュボードで R2 > あなたのバケットに移動
  2. Settings > Public access をクリック
  3. パブリックアクセスを有効にし、パブリックURLをメモ
  4. ストレージ設定を更新:
storage: r2({
  binding: "MEDIA",
  publicUrl: "https://pub-xxx.r2.dev"
}),

Cloudflare Access認証

組織がCloudflare Accessを使用している場合、パスキーの代わりに認証プロバイダーとして使用でき、既存のIDプロバイダーを通じてシングルサインオンを提供します。以下の設定で有効にします:

emdash({
  database: d1({ binding: "DB" }),
  storage: r2({ binding: "MEDIA" }),
  auth: access({
    teamDomain: "myteam.cloudflareaccess.com",
    audience: "your-app-audience-tag",
    roleMapping: {
      "Admins": 50,
      "Editors": 40,
    },
  }),
}),

すべての設定オプションについては、認証ガイドを参照してください。

メール

Workersでは、唯一の組み込み email:deliver ハンドラーは開発コンソールのスタブであるため、 メール依存のフロー — マジックリンクログイン、チーム招待、コメント 通知 — は本番環境で “Email is not configured” で失敗します。 cloudflareEmail() プラグインは、外部APIキーなしで、ネイティブの send_email Workerバインディングを使用して Cloudflare Email Sending 経由で実際のメールを配信します。

1. 送信者ドメインの登録

Cloudflareダッシュボードで Email に移動し、送信元のドメイン(またはアドレス)を 確認します。Email Sendingは未確認の送信者からのメッセージを拒否します。

2. バインディングの追加

wrangler.jsoncsend_email バインディングを宣言します:

{
	"send_email": [{ "name": "EMAIL" }],
}

3. プロバイダーの登録

emdash() インテグレーションにプラグインを追加します:

import { d1, r2 } from "@emdash-cms/cloudflare";
import { cloudflareEmail } from "@emdash-cms/cloudflare/plugins";

emdash({
	database: d1({ binding: "DB" }),
	storage: r2({ binding: "MEDIA" }),
	plugins: [
		cloudflareEmail({
			from: { email: "[email protected]", name: "My Site CMS" },
			replyTo: "[email protected]", // オプション
			binding: "EMAIL", // オプション、デフォルトは "EMAIL"
		}),
	],
}),

4. 有効化と選択

デプロイし、Admin → Extensions でプラグインを有効化し、 Settings → Email でプロバイダーとして選択します。

オプション

オプションデフォルト説明
fromstring | { email, name? }— (必須)Email Sending用に登録されたドメインの送信者アドレス。
replyTostringオプションのReply-To。from がno-replyサブドメインアドレスの場合に便利。
bindingstring"EMAIL"wrangler.jsoncsend_email バインディング名。

環境変数

推奨:暗号化キー

EMDASH_ENCRYPTION_KEY は、プラグインシークレットを保存時に暗号化するための キーです(webhookトークン、Turnstileキーなど)。キーは起動時に検証され、 プラグインシークレットの暗号化は有効化されると使用されます。すべてのデプロイで設定して、 後の設定変更なしにシークレットが保護されるようにしてください。

キーはあなたが提供し、データベースには保存されません。暗号化された 暗号文のみが保存されます。キーを失うと、それで暗号化されたすべてのシークレットを失います。

以下のコマンドでキーを生成し、Workerシークレットとして保存します:

npx emdash secrets generate
wrangler secret put EMDASH_ENCRYPTION_KEY

オプション:安定値のオーバーライド

EmDashはプレビューHMACシークレットとコメンターIPハッシュソルトを 自動生成し、初回使用時にデータベースに保存します。以下の環境変数は、 値を自分で固定する必要がある場合のオーバーライドです — 例えば、別プロセスのプレビューWorkerがメインサイトとシークレットを共有する必要がある場合。

変数目的
EMDASH_PREVIEW_SECRET自動生成されたプレビューHMACシークレットのオーバーライド。
EMDASH_IP_SALT自動生成されたコメンターIPハッシュソルトのオーバーライド。
EMDASH_AUTH_SECRETオプション。設定されると、IPソルトソースとして使用されます(EMDASH_IP_SALT も設定されている場合はそちらが優先)。すでにこれに依存しているインストールのコメンターIPハッシュを安定に保ちます。新しいデプロイでは未設定のままにしてください。

設定内の環境変数には import.meta.env またはCloudflareの env バインディングでアクセスします。

EmDashが使用するすべてのシークレットの完全なインベントリ — ストレージの場所、ローテーション手順、キーを失った場合に何が壊れるかを含む — については、シークレットとキー管理を参照してください。

プレビューデプロイメント

プレビューブランチをデプロイ:

wrangler deploy --env preview

wrangler.jsonc に環境セクションを追加:

{
	"env": {
		"preview": {
			"d1_databases": [
				{
					"binding": "DB",
					"database_name": "emdash-db-preview",
				},
			],
		},
	},
}

トラブルシューティング

”D1 binding not found”

wrangler.jsonc のバインディング名がデータベース設定と一致していることを確認します:

// 一致する必要があります: d1({ binding: "DB" })
"binding": "DB"

“R2 binding not found”

R2バケットが正しくバインドされていることを確認します:

// 一致する必要があります: r2({ binding: "MEDIA" })
"binding": "MEDIA"

マイグレーションエラー

スキーマエラーが表示される場合は、Workerログをtail(wrangler tail)してエラーを再現し、根本的なメッセージをキャプチャしてから、その出力でissueを作成してください。