本指南將 EmDash 網站部署到 Cloudflare Workers,使用 D1 作為資料庫、R2 作為媒體儲存。可從 EmDash Cloudflare 範本開始,或將相同設定套用到現有 Astro 網站。
先決條件
- Cloudflare 帳號
- 已安裝專案相依套件
- Wrangler 已通過 Cloudflare 驗證(
pnpm wrangler login)
設定繫結
Cloudflare 範本包含完整的 Worker 進入點,以及具名的 D1 與 R2 繫結。首次部署時,若設定的名稱尚不存在,Wrangler 會建立相應資源。請保留 wrangler.jsonc 中的名稱;Wrangler 會將後續部署重新連接到相同資源。
範本使用以下繫結:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-emdash-site",
"main": "./src/worker.ts",
"compatibility_date": "2026-02-24",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [
{
"binding": "DB",
"database_name": "my-emdash-site",
},
],
"r2_buckets": [
{
"binding": "MEDIA",
"bucket_name": "my-emdash-media",
},
],
"worker_loaders": [{ "binding": "LOADER" }],
"triggers": { "crons": ["* * * * *"] },
}
DB、MEDIA 和 LOADER 名稱必須與 EmDash 配接器相符。Cron Trigger 會執行排程發布、外掛任務、備份與維護。若網站使用沙箱外掛,請參閱 Plugin sandbox。
設定 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, sandbox } from "@emdash-cms/cloudflare";
export default defineConfig({
output: "server",
adapter: cloudflare(),
integrations: [
react(), // Required — the admin UI is a React app
emdash({
database: d1({ binding: "DB" }),
storage: r2({ binding: "MEDIA" }),
sandboxRunner: sandbox(),
}),
],
});
若網站不使用 marketplace、registry 或 sandboxed 外掛,可省略 sandboxRunner 與 LOADER 繫結。
新增 Worker 進入點
Worker 進入點會將 Astro 連接到 Cron Trigger,並匯出外掛橋接:
import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker";
export { PluginBridge };
export default {
...handler,
scheduled: createScheduledHandler(),
} satisfies ExportedHandler;
未安裝沙箱外掛時,匯出 PluginBridge 不會造成任何影響。若同一專案稍後可能啟用外掛,請保留它。
若要以非每分鐘一次的排程執行一般維護,請將同一個 Cron 運算式傳給 createScheduledHandler({ generalCron: "..." }) 和 triggers.crons。若兩者不同,處理常式會記錄並忽略非預期的觸發。
建置與部署
建置並部署網站一次,讓 Wrangler 佈建具名的 D1 資料庫與 R2 儲存貯體。Wrangler 使用 pnpm wrangler login 建立的本機登入。
pnpm build
pnpm wrangler deploy
在預設的 auto 遷移模式下,已部署的 Worker 收到第一個請求時,EmDash 會套用待處理的核心遷移。Worker 會把大型內容遷移留給 emdash migrate,並在這些遷移套用之前以 503 回應回覆。當部署管線必須在新程式碼接收流量之前套用遷移,或需要檢查、核對或復原遷移時,請使用 Manage core database migrations。
在設定精靈完成之前,EmDash 還會在第一個請求時套用一次 seed 檔案的 schema 與結構。只有當管理員在設定精靈中選擇時,才會套用 seed 中的範例內容。Seed 會在建置時從 .emdash/seed.json、package.json#emdash.seed 中的路徑或 seed/seed.json(以先找到的為準)讀取,並內嵌到套件中。若都不存在,則使用內建的預設 seed。對既有資料庫的後續部署不會變動其內容。
若要變更已部署網站的 schema 或內容模型,請參閱 Evolving a Deployed Site。
排程發布
在 Cloudflare 上,EmDash 只會透過 Worker 的 scheduled 處理常式發布已排程的項目,Cloudflare 會依照 Cron Trigger 的排程呼叫該處理常式。已部署的 Worker 需要同時具備以下兩項:
wrangler.jsonc的triggers.crons清單中有一個 Cron Trigger,如設定繫結所示。- 來自
@emdash-cms/cloudflare/worker的scheduled處理常式,從main指向的 Worker 進入點匯出,如新增 Worker 進入點所示。
若缺少其中任何一項,已排程的項目都不會上線。在使用 astro dev 的本機開發中,EmDash 會以計時器發布已排程的項目,因此在本機可行的排程並不能證明已部署的設定正確。
在專案根目錄執行 emdash doctor,檢查本機專案檔案中的 Cron Trigger 與 scheduled 處理常式:
pnpm emdash doctor
當 emdash doctor 同時找到兩者時,輸出會包含一項通過的 scheduler wiring 檢查。失敗的排程器檢查會描述問題並印出修正方式。該檢查能辨識本頁所示的 Worker 進入點,以及它作為修正方式印出的單行重新匯出,因此以其他方式撰寫的進入點即使能正常運作,也可能無法通過該檢查。
若沒有出現排程器檢查,表示 emdash doctor 在目前目錄中找不到 Wrangler 設定,或是既找不到 Cron Trigger 也找不到 scheduled 處理常式。在 D1 專案上,database 檢查會失敗,因為它尋找的是本機 SQLite 檔案(預設為 ./data.db)。該失敗不影響排程器檢查。
新增或變更 Cron Trigger 或 scheduled 處理常式後,請重新部署。Cloudflare 最多可能需要 15 分鐘才會開始執行新的或已變更的 Cron Trigger。觸發器開始執行後,EmDash 會發布排程時間已過的項目。
若要觀察觸發器,請執行 pnpm wrangler tail。每次執行都會印出類似 "* * * * *" @ 9/29/2026, 10:01:00 AM - Ok 的一行。Ok 表示該次執行結束時沒有未捕捉的錯誤。EmDash 會捕捉並記錄發布錯誤,因此 Ok 並不能確認項目已發布。發布了項目的執行還會記錄類似 [scheduled] Published 1 scheduled item(s) 的訊息,該次執行的錯誤會出現在同一份輸出中。
部署後,當某個已排程的項目已超過其排程時間,且最近五分鐘內沒有完成任何排程器執行時,管理儀表板會顯示 Scheduled publishing needs attention。如果 Cron Trigger 的執行頻率低於每五分鐘一次,該提示也可能在兩次執行之間出現,直到下一次執行發布該項目。
將 Worker 放在靠近 D1 的位置
Cloudflare 預設在靠近訪客處執行 Worker。EmDash 的伺服器端轉譯請求會多次往返 D1,因此請使用 Targeted Placement,在靠近 D1 主要資料庫處執行 Worker,以加快這些請求。
Wrangler 接受恰好帶有一個選擇器的 placement.mode: "targeted":region、host 或 hostname。請選擇指向 D1 主要資料庫位置的值,並將得到的 placement 物件加入 wrangler.jsonc。請勿將 D1 唯讀複本與 Targeted Placement 一起啟用。沒有複本時,每個查詢都使用附近的主要資料庫,包括使用 session: "auto" 與 coalesce: true 時。
物件快取
為降低 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 Cache 會在 Worker 前面放置邊緣快取:相符的請求會在完全不執行 Worker 的情況下被提供。
啟用
使用 Astro 的 Cloudflare 快取提供者,使路由規則與 Astro.cache 設定快取標頭,並讓提供者能在內容變更時使項目失效。
import { cacheCloudflare } from "@astrojs/cloudflare/cache";
export default defineConfig({
adapter: cloudflare(),
cache: {
provider: cacheCloudflare(),
},
routeRules: {
"/": { maxAge: 300, swr: 86400, tags: ["posts", "pages"] },
// 其他公開路由可以使用不同的快取時間長度。
},
});
@astrojs/cloudflare 配接器會偵測 cacheCloudflare(),並在產生的部署設定中啟用 Workers Cache。
內容變更時,EmDash 會自動使快取項目失效(標籤慣例請參閱路由快取)。只有對於自訂標籤,例如手動的「清除全部」開關,你才需要自行清除,方法是在 Worker 中使用平台的清除 API。它不需要 Cloudflare REST 憑證。
公開頁面透過 routeRules 和 Astro.cache 控制自身的快取。請僅為你的公開路由設定路由規則:網站根路徑上的剩餘參數,例如 /[...slug],同樣會比對 /_emdash/ 下的管理後台和 API 路由。
啟用前須知兩點:
- 帶有任何快取提示的回應都是可快取的,即使沒有路由規則。 配接器會把沒有任何路由規則或
Astro.cache.set()呼叫將其設為可快取的回應標記為no-store。帶有標籤但沒有maxAge的提示,例如版面配置在每個頁面上設定的網站設定和選單提示,會產生不帶有效期的Cloudflare-CDN-Cache-Control: public,因此 Workers Cache 會回退到它的預設快取時長(200回應為 2 小時)。請為每個公開路由提供規則,並讓依賴工作階段的路由不帶任何快取提示。 - 快取頁面會與已登入編輯者共用。 快取在 Worker 之前執行,因此無法根據請求 cookie 旁路。使用預設的伺服器端轉譯工具列時,已登入編輯者可能收到公開頁面的快取匿名變體(沒有視覺化編輯工具列),直到項目過期。請設定
toolbar: "client",以便在快取頁面上保持工具列可靠。為已登入使用者轉譯的頁面絕不會存入路由快取。
如需更深入的指南,涵蓋路由規則、快取標籤、EmDash 在發佈時的內建失效機制,以及必須讓其不進入快取的回應,請參閱路由快取。
Workers Caching 與物件快取(objectCache: kvCache({ binding: "CACHE" }))是分開的,後者在 Worker 之下的 KV 中快取資料庫查詢結果。
自訂網域
首次部署會取得 workers.dev URL。自訂網域必須已是與 Worker 位於同一帳號、由 Cloudflare 管理的有效網域。Worker 在其 workers.dev URL 成功回應後,將正式環境網域新增為 Wrangler 路由:
{
"routes": [{ "pattern": "www.example.com", "custom_domain": true }],
}
再次部署並驗證兩個位址。在測試 DNS 時保持 workers.dev 位址可用,有助於區分路由問題與應用程式問題。若要讓新網域成為網站地圖和搜尋中繼資料中的位址,以及在未設定 siteUrl、EMDASH_SITE_URL 或 SITE_URL 時郵件和外掛程式中使用的位址,請使用 Settings > General 中的 Change domain;參見遷移到新網域。
如果沒有設定 siteUrl、EMDASH_SITE_URL 或 SITE_URL,設定會將其執行所在的位址記錄為網站的來源,設定期間建立的通行金鑰只能在該位址上使用。請在正式環境網域上執行設定,或在設定之前將 EMDASH_SITE_URL 設為該網域。設定之後若將 EMDASH_SITE_URL 改為另一個網域,登入會轉移到該網域,而在舊位址上建立的通行金鑰將無法在新網域上使用。
公開 R2 存取
預設情況下,媒體透過 EmDash 的已驗證媒體路由提供。若儲存貯體有公開的自訂網域,請將該來源設為 publicUrl,讓產生的媒體 URL 使用它:
storage: r2({
binding: "MEDIA",
publicUrl: "https://media.example.com",
}),
公開儲存貯體存取適用於每個可連到的物件,而不只是媒體。自動 JSON 備份在同一個儲存後端使用 backups/ 前綴,因此不要透過公開網域暴露該前綴。Choose media storage 說明了安全邊界。
影像轉換
EmDash 透過 Cloudflare 的 IMAGES 繫結,在 Worker 內調整大小並重新編碼 R2 媒體。emdash/ui 的 Image 元件與富文字中的影像,都會透過 EmDash 在 Cloudflare 配接器下安裝的影像端點轉譯。對於內部路由 /_emdash/api/media/file/… 上的媒體,該端點會直接從 R2 繫結讀取來源位元組,無需 HTTP 擷取。這些轉換在 Cloudflare Access 之後以及使用 global_fetch_strictly_public 時仍可運作。從儲存貯體 URL 提供的媒體——請參閱公開 R2 存取——則改用配接器自己的轉換端點,該端點會在轉換前透過 HTTP 擷取檔案。
你不必宣告該繫結。@astrojs/cloudflare 會在 astro build 期間將它加入所產生的 Worker 設定,方式與為 Workers Caching 加入 cache 相同。每當執行階段影像服務為 cloudflare-binding 時就會如此:imageService 未設定、為該字串本身,或為 { runtime: "cloudflare-binding" }。其他任何值——"passthrough"、"compile"、"cloudflare"、"custom"——都會省略該繫結。在自己的 wrangler.jsonc 中列出它,可讓意圖更明確:
{
"images": {
"binding": "IMAGES",
},
}
若要查看部署實際取得什麼,請閱讀產生的設定,而不是 wrangler.jsonc。建置會寫入 .wrangler/deploy/config.json,它會將 wrangler deploy 指向合併後的檔案(預設為 dist/server/wrangler.json)。請在那裡尋找 images 項目。
Cloudflare 會將這些轉換依 Images transformations 計費。來源影像與參數的每個唯一組合,每個日曆月計費一次,該月內的重複請求免費。若網站有 500 張來源影像,且每張都請求一種縮圖尺寸和一種主圖尺寸,則這兩組參數在該月會計為 1,000 張轉換後的影像。Images Free 方案每月涵蓋 5,000 次唯一轉換。超過該上限後,已快取的轉換仍會提供,但新的轉換會回傳 9422 錯誤,影像請求隨之失敗。
Cloudflare Access 驗證
Cloudflare Access 可用附加到 Access 應用程式的身分提供者取代通行金鑰驗證。受眾值是機密的執行階段設定;請透過命名其環境變數,將它排除在 astro.config.mjs 之外:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audienceEnvVar: "CF_ACCESS_AUDIENCE",
roleMapping: {
Admins: 50,
Editors: 40,
},
}),
}),
使用 pnpm wrangler secret put CF_ACCESS_AUDIENCE 設定 CF_ACCESS_AUDIENCE。authentication guide 說明使用者佈建、預設角色與角色同步。
電子郵件
正式環境的 Worker 沒有預設的電子郵件寄送服務。在電子郵件外掛啟用之前,魔術連結登入會回傳 Email is not configured。
Cloudflare 電子郵件外掛使用 send_email 繫結。請先透過 Cloudflare Email Sending 完成寄件者網域的註冊與驗證。Cloudflare 會拒絕 From 位址不是已接受寄件者的郵件。
新增繫結並註冊提供者:
{
"send_email": [{ "name": "EMAIL" }],
}
import { cloudflareEmail } from "@emdash-cms/cloudflare/plugins";
emdash({
plugins: [
cloudflareEmail({
from: { email: "[email protected]", name: "My Site CMS" },
replyTo: "[email protected]",
}),
],
}),
該外掛自首次部署起即處於啟用狀態。Settings → Email 會顯示所選的提供者,並可寄送測試郵件。在寄件者被接受且繫結存在之前,寄送都會失敗。
除非其 binding 選項指定了其他名稱,否則外掛會使用名為 EMAIL 的繫結。若它是唯一啟用中的電子郵件提供者,EmDash 會自動選取它。若有多個提供者啟用,請在 Settings → Email 下選擇 Cloudflare 提供者。選用的 replyTo 位址可在不變更已接受之 From 位址的情況下接收回覆。外掛可為單一訊息設定 replyTo,這會覆寫該訊息的此選項。
Cloudflare AI Search
要為站台新增 Cloudflare AI Search,請安裝 AI Search 外掛。
Worker 密鑰
使用 pnpm wrangler secret put <NAME> 儲存密鑰值。請勿將它們放入 wrangler.jsonc,也不要從建置時的 import.meta.env 值讀取。
EMDASH_ENCRYPTION_KEY 會加密宣告為密鑰的外掛設定。請在儲存外掛密鑰之前設定它,並與 D1 備份分開保存。輪替期間,請先提供新金鑰,並在逗號後保留較舊的金鑰,直到每個外掛密鑰都已重新儲存。Secrets and key management 說明了輪替與復原。
外掛橋接會直接讀取此 Worker 密鑰繫結。產生的管理設定路由則透過 process.env 讀取它。使用 nodejs_compat 時,對於相容性日期為 2025-04-01 或之後的專案,Cloudflare 預設會填入 process.env。固定在更早日期的專案,在儲存加密設定之前還必須加入 nodejs_compat_populate_process_env。
EmDash 在執行階段從 process.env 讀取密鑰。Worker 程式碼則從 cloudflare:workers 匯入的 env 讀取繫結。切勿透過 import.meta.env 讀取密鑰:Vite 會在建置時取代這些值,並可能將它們寫入伺服器套件。
預覽 HMAC 密鑰與留言者 IP salt 除非你提供執行階段覆寫,否則會產生並儲存在資料庫中。Secrets and key management 列出確切的變數、儲存位置與輪替影響。
預覽部署
預覽既可以作為 Worker Preview 執行(它會在正式環境 Worker 上為每個分支提供獨立的 URL),也可以作為具名 Wrangler 環境中的獨立 Worker 執行。無論哪種方式,預覽都需要與正式環境分開的資料庫和儲存貯體。切勿將預覽繫結指向正式環境的資料庫或儲存貯體。
Worker Previews
Worker Previews 要求專案相依項目中的 Wrangler 為 4.135.0 或更新版本。由較舊的 EmDash 範本建立的專案可能鎖定了更早的版本;請使用 pnpm add -D wrangler@latest 升級。預覽不會繼承頂層設定中的繫結或 vars;請在 previews 區塊中列出預覽使用的每個繫結。
Astro Cloudflare 配接器會新增一個 SESSION KV 繫結。wrangler deploy 會自動佈建它,但 wrangler preview 在沒有命名空間 ID 時會拒絕它。請建立預覽資源,包括用於工作階段的 KV 命名空間:
pnpm wrangler d1 create my-emdash-site-preview
pnpm wrangler r2 bucket create my-emdash-media-preview
pnpm wrangler kv namespace create my-emdash-site-preview-session
將 Wrangler 輸出的資源識別碼新增到 wrangler.jsonc:
{
"previews": {
"d1_databases": [
{
"binding": "DB",
"database_name": "my-emdash-site-preview",
"database_id": "00000000-0000-0000-0000-000000000000",
},
],
"r2_buckets": [
{
"binding": "MEDIA",
"bucket_name": "my-emdash-media-preview",
},
],
"kv_namespaces": [{ "binding": "SESSION", "id": "00000000000000000000000000000000" }],
"worker_loaders": [{ "binding": "LOADER" }],
},
}
當預覽使用 send_email、AI Search、vars 及其他功能時,請重複宣告這些繫結。
設定精靈和通行金鑰需要預覽的站台 URL。如果沒有 siteUrl 或 EMDASH_SITE_URL,設定會失敗並報 SITE_URL_REQUIRED。每次部署預覽時,都要傳入預覽 URL https://<preview-name>-<worker-name>.<subdomain>.workers.dev:
pnpm build
pnpm wrangler preview --name my-branch \
--var EMDASH_SITE_URL:https://my-branch-my-emdash-site.example.workers.dev
請在 --name 和 URL 中使用相同的預覽名稱。astro.config.mjs 中的 siteUrl 會覆寫 EMDASH_SITE_URL,並被建置進每個預覽,因此預覽上的通行金鑰會繫結到正式環境網域,設定和登入在預覽上都無法使用。請改為在頂層 vars 中用 EMDASH_SITE_URL 設定正式環境 URL。
如果 Worker 將 workers_dev 設為 false,則在你於 wrangler.jsonc 頂層設定 "preview_urls": true 並執行一次 wrangler deploy 之前,預覽沒有 URL。wrangler preview 不會套用這項設定。wrangler deploy 會把目前簽出中的程式碼(而不只是這項設定)發佈到正式環境,因此請提交這項變更,並在正式環境分支的乾淨簽出中部署。然後回到你的分支,再次執行 wrangler preview。
用 pnpm wrangler preview secret put <NAME> --name my-branch 為某一個預覽設定密鑰。pnpm wrangler preview base-config secret put <NAME> 會為此後建立的預覽設定密鑰;既有的預覽保留它們原來的密鑰。
請留意以下與正式環境的差異:
- 預覽共用
previews區塊中的資源。wrangler preview讀取其所部署分支的previews區塊,因此所有沿用基礎分支設定的分支都使用同一個資料庫。對預覽的第一次請求會套用待處理的核心遷移,所以升級 EmDash 的分支也會遷移其他預覽所使用的資料庫。若要隔離某個分支,請再建立一個 D1 資料庫,並在部署前修改該分支previews區塊中的database_name和database_id。 - 空的預覽資料庫會顯示設定精靈。 預覽 URL 是公開的,第一個完成精靈的訪客會成為管理員。請在建立預覽後立即完成設定,或用 Cloudflare Access 保護預覽 URL。
- Cron Triggers 不會執行。 排程文章不會發佈,外掛程式的 cron 任務和排程備份也不會在預覽上執行。
- 通行金鑰只在註冊它的主機名稱上有效。 正式環境的通行金鑰在預覽上無效,在第一個預覽上建立的管理員只能在該預覽 URL 上用通行金鑰登入。在共用該資料庫的另一個預覽上,請使用魔法連結登入(它需要郵件提供方及其在
previews區塊中的繫結),並在那裡新增通行金鑰。或者,如上所述為該分支單獨使用一個資料庫來隔離它,並在其上執行設定。
具名環境
具名的 Wrangler 環境不會繼承繫結。請建立獨立的預覽資源,並在建置前將它們寫入 preview 環境:
pnpm wrangler d1 create my-emdash-site-preview \
--binding DB --env preview --update-config
pnpm wrangler r2 bucket create my-emdash-media-preview \
--binding MEDIA --env preview --update-config
預覽環境必須重複預覽 Worker 使用的每個繫結。在 Wrangler 寫入資源識別碼後,核心 D1、R2 與沙箱繫結具有以下形式:
{
"env": {
"preview": {
"d1_databases": [
{
"binding": "DB",
"database_name": "my-emdash-site-preview",
"database_id": "00000000-0000-0000-0000-000000000000",
},
],
"r2_buckets": [
{
"binding": "MEDIA",
"bucket_name": "my-emdash-media-preview",
},
],
"worker_loaders": [{ "binding": "LOADER" }],
},
},
}
請使用 Wrangler 寫入的預覽 UUID。當預覽使用這些功能時,請重複選用的 KV、AI Search、電子郵件及其他繫結。使用 pnpm wrangler secret put <NAME> --env preview 新增僅限預覽的密鑰。
建置並部署預覽環境。其第一個請求會透過預設的 auto 模式套用待處理的核心遷移。
pnpm build
pnpm wrangler deploy --env preview
在分享之前,請驗證預覽 URL、管理登入、媒體上傳,以及任何選用繫結。
驗證部署
部署後,請求一個公開頁面,登入 /_emdash/admin,上傳並取回一個測試媒體檔案,並確認 scheduled 處理常式出現在 pnpm wrangler tail 中。
疑難排解
「D1 binding not found」
請確認 wrangler.jsonc 中的繫結名稱與資料庫設定相符:
// Must match: d1({ binding: "DB" })
"binding": "DB"
「R2 binding not found」
請檢查 R2 儲存貯體是否已正確繫結:
// Must match: r2({ binding: "MEDIA" })
"binding": "MEDIA"
遷移錯誤
若看到 schema 錯誤,請追蹤 Worker 日誌(wrangler tail)並重現錯誤以取得底層訊息——然後附上該輸出提交 issue。