部署到 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(), // 必需 — 管理介面是一個 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 上線。

讀取副本

對於全球分散的網站,啟用 D1 讀取複製以將讀取查詢路由到鄰近的副本,而不是總是存取主資料庫。這顯著降低了遠離主區域的訪客的延遲。

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

你還需要在 Cloudflare 儀表板或透過 REST API 在 D1 資料庫本身上啟用讀取複製。

有關工作階段模式和基於書籤的一致性如何運作,請參閱資料庫選項 — 讀取副本

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 標頭控制自己的快取。

啟用前需要了解的兩點:

  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,你可以將其用作認證提供者來取代通行金鑰,透過現有的身分提供者實現單一登入。以下設定可以啟用它:

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() 外掛透過 Cloudflare Email Sending 使用原生的 send_email Worker 綁定來傳送真實電子郵件,無需外部 API 金鑰。

1. 註冊傳送網域

在 Cloudflare 儀表板中,前往 Email 並驗證你傳送郵件的網域(或地址)。 Email Sending 會拒絕來自未驗證傳送者的訊息。

2. 新增綁定

wrangler.jsonc 中宣告一個 send_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 記錄(wrangler tail)並重現錯誤以擷取底層訊息 — 然後用該輸出提交 issue。