部署到 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"

迁移错误

如果你看到 schema 错误,跟踪 Worker 日志(wrangler tail)并重现错误以捕获底层消息 — 然后用该输出提交 issue。