EmDash 的主要配置位于 astro.config.mjs,而 src/live.config.ts 用于注册内容加载器。与部署相关的值也可以来自环境变量。package.json 中的一小段元数据块支持模板标签和旧版本地 CLI 流程。
Astro 集成
在 astro.config.mjs 中将 EmDash 配置为 Astro 集成:
import { defineConfig } from "astro/config";
import emdash, { local, s3 } from "emdash/astro";
import { sqlite, libsql } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
plugins: [],
}),
],
});
集成选项
database
必填。 数据库适配器配置。选择一个适配器:
// SQLite (Node.js)
database: sqlite({ url: "file:./data.db" });
// PostgreSQL
database: postgres({ connectionString: process.env.DATABASE_URL });
// libSQL
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
// Cloudflare D1 (import from @emdash-cms/cloudflare)
database: d1({ binding: "DB" });
详情请参阅数据库选项。
migrations
可选。 控制 EmDash 内部数据库迁移在运行时的处理方式。省略此选项时,默认值为 { runtime: "auto" }。
migrations: {
runtime: "check", // "auto" | "check" | "manual"
dev: "auto", // optional development override
}
auto 会检查并应用待处理的迁移;check 在正在运行的构建已知存在待处理迁移时返回 503(EmDash 安装的图像端点的请求除外);manual 在运行时不执行任何迁移查询。EMDASH_MIGRATIONS_MODE 会覆盖实际生效的运行时模式。在采用 check 或 manual 之前,请先参阅管理核心数据库迁移。
storage
可选。 媒体存储适配器配置。省略此选项时,EmDash 会将文件存储在 ./.emdash/uploads 中,并通过 /_emdash/api/media/file 提供访问。当默认的本地目录不适用时,请选择一个适配器:
// Local filesystem (development)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// R2 binding (Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // optional
});
// S3-compatible (any platform) — all fields from S3_* environment variables
storage: s3()
// Or with explicit values
storage: s3({
endpoint: "https://s3.amazonaws.com",
bucket: "my-bucket",
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "us-east-1", // optional, default: "auto"
publicUrl: "https://cdn.example.com", // optional
});
详情请参阅存储选项。
images
可选。 控制 EmDash 是否将已存储的媒体与 Astro 的图像优化集成。默认值为 true。
启用后,EmDash 会包装 Astro 的图像端点,使 <Image> 和 getImage() 可以直接从已配置的存储适配器读取源字节。当原始媒体 URL 位于 Cloudflare Access 之后时,这同样有效。对被包装端点的请求会跳过 EmDash 的运行时启动,因此在这些请求中 locals.emdash 只包含 storage,已登录用户还会包含 db。locals.user 的设置方式与其他公开路由相同。当由其他图像服务处理媒体,或需要在不经过 EmDash 端点包装器的情况下渲染每张图像时,请设置 images: false。
emdash({
images: false,
});
mediaProviders
可选。 向媒体库添加媒体服务。基于存储的本地提供者会自动保持可用;此数组中的每个描述符都会为编辑者增加一个可浏览或上传媒体的位置。
下面的示例添加 Cloudflare Images 和 Cloudflare Stream:
import { cloudflareImages, cloudflareStream } from "@emdash-cms/cloudflare";
emdash({
mediaProviders: [cloudflareImages({}), cloudflareStream({})],
});
提供者的凭据在运行时解析。上面的空配置使用 cloudflareImages(config) 和 cloudflareStream(config) 适配器章节中描述的默认 Cloudflare 环境变量。有关绑定和渲染设置,请参阅媒体库:媒体提供者。
objectCache
可选。 将内容和配置的查询结果缓存到键/值存储中,使读取无需在每次请求时查询数据库。省略时禁用。选择一个适配器:
// Cloudflare KV (shared across all isolates)
import { kvCache } from "@emdash-cms/cloudflare";
objectCache: kvCache({ binding: "CACHE" });
// In-memory (Node.js / development)
import { memoryCache } from "emdash/astro";
objectCache: memoryCache();
设置与选项请参阅对象缓存。
middleware.outer
可选。 在整个 EmDash 中间件栈之外注册一个 Astro 中间件模块。由于集成使用 Astro 的 order: "pre" 注册它,它也会先于 src/middleware.ts 中定义的中间件运行。可用于需要在缓存命中时避免运行时和数据库初始化的请求拦截或完整响应缓存,也可用于依赖 EmDash 最终 HTML 的响应标头。
emdash({
middleware: {
outer: "./src/outer-middleware.ts",
},
});
执行顺序如下:
- 外层中间件运行到
await next()。 - EmDash 初始化其运行时和数据库,然后运行设置、身份验证和请求上下文中间件。
- Astro 路由渲染。
- EmDash 应用对响应的修改,包括可视化编辑 HTML 以及安全/计时标头。
next()带着该最终响应解析回外层中间件。
在调用 next() 之前,中间件拥有常规的 Astro 请求和平台执行上下文,但 locals.emdash、locals.user、数据库以及请求范围内的 EmDash 状态均不可用。提前返回的 Response 会完全跳过 EmDash,因此必须自行包含所需的安全和缓存标头。next() 解析之后,可以安全地最终确定 CSP nonce、缓存完整正文或设置 Content-Length。如果中间件修改了正文,请移除或重新计算任何已有的 Content-Length 标头。
该钩子在 Node 和 Cloudflare 上都使用 Astro 的中间件 API。下面这个最小化的 Cloudflare Cache API 示例只缓存匿名 HTML 响应,并在 EmDash 初始化之前返回命中结果:
import { waitUntil } from "cloudflare:workers";
import { defineMiddleware } from "astro:middleware";
export const onRequest = defineMiddleware(async ({ request }, next) => {
if (request.method !== "GET" || request.headers.has("cookie")) {
return next();
}
const cacheKey = new Request(request.url, { method: "GET" });
const cached = await caches.default.match(cacheKey);
if (cached) return cached;
const response = await next();
const isHtml = response.headers.get("content-type")?.includes("text/html");
const isPrivate = response.headers.get("cache-control")?.includes("no-store");
if (response.ok && isHtml && !isPrivate) {
waitUntil(caches.default.put(cacheKey, response.clone()));
}
return response;
});
在 Node 上,请使用相同形态的中间件,并搭配 Redis 等兼容 Node 的缓存。缓存键和绕过规则必须包含会改变渲染响应的每一个请求属性。
playground
可选。 启用一次性、基于浏览器的 EmDash 演练场所使用的中间件。它会为每个会话创建一个可写的 Durable Object 数据库,应用配置的种子数据,并在常规 EmDash 中间件运行之前将访客以匿名管理员身份登录。
import { playgroundDatabase } from "@emdash-cms/cloudflare";
emdash({
database: playgroundDatabase({ binding: "PLAYGROUND_DB" }),
playground: {
middlewareEntrypoint: "@emdash-cms/cloudflare/db/playground-middleware",
},
});
此模式需要 @emdash-cms/cloudflare 和一个 Durable Object 绑定。它会绕过常规的设置和身份验证中间件,因此只应用于临时演示站点,而不是生产环境的 CMS。
plugins
可选。 与 Astro 站点在同一进程中运行的插件数组。原生插件应放在这里。当你信任某个与沙箱兼容的插件拥有完整的进程访问权限且不需要隔离时,它也可以在这里运行。
下面的示例注册一个原生插件:
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
原生插件可以直接使用服务端和框架 API,因此除非该包同时提供与沙箱兼容的插件入口,否则无法移到 sandboxed 中。有关开发和部署方面的差异,请参阅选择插件格式。
sandboxed
可选。 与沙箱兼容的插件数组,这些插件使用 EmDash 声明的插件 API,并在隔离的运行时中运行。请勿在此处放置原生插件:原生代码可能依赖沙箱不提供的进程和框架访问权限。
import thirdPartyPlugin from "third-party-emdash-plugin";
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxed: [thirdPartyPlugin()],
sandboxRunner: sandbox(),
});
当未配置可用的沙箱运行器时,沙箱化插件会被跳过。有关 Cloudflare 和 Node.js 运行器的设置,请参阅插件沙箱。
sandboxRunner
可选。 启动隔离插件运行时的工厂的模块说明符。sandboxed 中的插件以及市场或注册表插件都需要它。
在 Cloudflare Workers 上,使用 sandbox() 适配器:
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
});
Node.js 部署使用插件沙箱:Node.js 中记录的 workerd 运行器模块。
sandbox
可选。 控制已配置的沙箱运行器是否隔离插件。配置了 sandboxRunner 时,沙箱即启用。仅在诊断问题出在插件本身还是其沙箱运行时的情况下,才设置 sandbox: false:
emdash({
sandboxRunner: sandbox(),
sandbox: false,
});
设为 false 时,在 sandboxed 中声明的插件以及从市场安装的插件会在主服务器进程中运行,没有隔离也没有资源限制。诊断完成后请恢复沙箱。
registry
可选。 配置插件注册表的聚合器和策略。未显式设置时,如果配置了 sandboxRunner 且 sandbox 不是 false,EmDash 会使用 https://registry.emdashcms.com。
设置 registry: false 可禁用注册表发现和通过注册表安装的插件,同时保留沙箱运行器,供在 sandboxed 中声明的插件和旧版市场插件使用:
emdash({
sandboxRunner: sandbox(),
registry: false,
});
可以将注册表服务 URL 作为字符串传入;当站点需要审核来源或发布时长策略时,使用对象形式。下面的示例使用对象形式:
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
registry: {
aggregatorUrl: "https://registry.emdashcms.com",
acceptLabelers: "did:web:labels.emdashcms.com",
policy: {
minimumReleaseAge: "48h",
minimumReleaseAgeExclude: ["did:plc:yourfirstpartydid"],
},
},
});
| 选项 | 类型 | 说明 |
|---|---|---|
aggregatorUrl | string | 注册表服务的基础 URL。生产环境请使用 HTTPS。 |
acceptLabelers | string | 请求所接受的审核服务的去中心化标识符(DID),以逗号分隔,可选。DID 是稳定的 Atmosphere 账号标识符。此设置无法覆盖注册表服务的策略。 |
policy.minimumReleaseAge | string | number | 暂缓发布比此时长更新的版本。时长字符串("48h"、"7d")或秒数。 |
policy.minimumReleaseAgeExclude | string[] | 不受暂缓限制的发布者 DID,或 <did>/<plugin-slug> 组合。 |
仅当注册表报告只保留了一个版本,并确认其持续观察过该包时,发布时长策略才会豁免包的首个版本。回填的包、被删除的早期版本或缺少历史证据,都会使暂缓继续生效。显式的发布者和包豁免不论历史如何都适用。
有关安装流程和信任模型,请参阅插件注册表。
marketplace
已弃用。 用于更新从旧版市场安装的插件的基础 URL。管理后台不会显示市场浏览和新安装。只要配置了此选项,现有的市场插件仍可更新和卸载。
emdash({
marketplace: "https://marketplace.emdashcms.com",
sandboxRunner: sandbox(),
});
生产环境的 URL 必须使用 HTTPS;开发期间仅对 localhost 和 127.0.0.1 接受 HTTP。在所有市场插件都被替换或卸载之前请保留此选项,之后再将其移除。完整流程请参阅从市场迁移。
fonts
可选。 管理后台界面的字体配置。
默认情况下,EmDash 通过 Astro Font API 加载 Noto Sans。字体在构建时从 Google 下载并自托管,因此运行时没有 CDN 请求。基础字体涵盖拉丁文、西里尔文、希腊文、天城文和越南文字。
要添加对其他书写系统的支持,请传入文字名称。下面的示例添加阿拉伯文和日文:
emdash({
fonts: {
scripts: ["arabic", "japanese"],
},
})
可用的文字包括 arabic、armenian、bengali、chinese-simplified、chinese-traditional、chinese-hongkong、devanagari、ethiopic、farsi、georgian、gujarati、gurmukhi、hebrew、japanese、kannada、khmer、korean、lao、malayalam、myanmar、oriya、sinhala、tamil、telugu、thai 和 tibetan。
每种文字对应 Google Fonts 上相应的 Noto Sans 变体(例如 "arabic" 加载 Noto Sans Arabic)。所有字体样式共用同一个 font-family 名称并使用 unicode-range,因此浏览器只会下载页面字符所需的文件。
设为 false 可完全禁用字体注入并使用系统字体:
emdash({
fonts: false,
})
管理后台的 CSS 使用 --font-emdash CSS 变量。它由上述字体配置自动设置。
auth
可选。 身份验证适配器。EmDash 内置的登录方式是通行密钥;设置 auth 会用外部提供者替换它。Cloudflare Access 适配器 access() 由 @emdash-cms/cloudflare 提供:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
roleMapping: {
Admins: 50,
Editors: 40,
},
}),
});
access() 的选项:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
teamDomain | string | 必填 | 你的 Cloudflare Access 团队域名 |
audience | string | — | 应用程序 Audience(AUD)标签。在 Workers 上,建议使用 audienceEnvVar。 |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | 运行时从中读取 audience 标签的环境变量 |
autoProvision | boolean | true | 首次登录时创建 EmDash 用户 |
defaultRole | number | 30 | 未被 roleMapping 匹配的用户的角色级别(参见用户角色) |
syncRoles | boolean | false | 每次登录时重新应用 roleMapping,而不是仅在创建账号时应用 |
roleMapping | object | — | 将 IdP 组名映射到 EmDash 角色级别;先匹配者优先 |
authProviders
可选。 可插拔登录提供者的数组(位于顶层,与 auth 并列)。每个条目都是调用某个提供者工厂函数的结果,如下所示:
import { github } from "emdash/auth/providers/github";
import { google } from "emdash/auth/providers/google";
import { atproto } from "@emdash-cms/auth-atproto";
emdash({
authProviders: [github(), google(), atproto()],
});
内置提供者:
github()— 读取EMDASH_OAUTH_GITHUB_CLIENT_ID/EMDASH_OAUTH_GITHUB_CLIENT_SECRET(或不带前缀的备用变量)。google()— 读取EMDASH_OAUTH_GOOGLE_CLIENT_ID/EMDASH_OAUTH_GOOGLE_CLIENT_SECRET。microsoft()— 读取EMDASH_OAUTH_MICROSOFT_CLIENT_ID/EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET/EMDASH_OAUTH_MICROSOFT_TENANT_ID(或不带前缀的备用变量)。接受{ emailVerified }。参见 Microsoft。atproto()— Atmosphere 账号登录(Bluesky 以及更广泛的 AT Protocol 网络)。无需环境变量。接受{ allowedDIDs, allowedHandles, defaultRole }。参见 Atmosphere 登录指南。
第三方包可以使用相同的 AuthProviderDescriptor 结构注册自己的提供者——参见登录提供者。
mcp
可选。 在 /_emdash/api/mcp 启用 Model Context Protocol(MCP)端点。该端点默认启用且需要 Bearer 令牌,因此启用它并不会授予匿名访问权限。
当站点不得暴露 MCP 端点时,将该选项设为 false:
emdash({
mcp: false,
});
有关令牌创建和客户端配置,请参阅 MCP 服务器参考。
siteUrl
站点面向浏览器的公开源(协议 + 主机 + 可选端口,不含路径)。请在运行生产环境设置之前配置它,除非站点部署到 Cloudflare Workers。否则,只有环回开发主机才能在未配置源的情况下完成设置。
在终止 TLS 的反向代理之后,Astro.url 会返回内部地址(http://localhost:4321),而不是公开地址(https://cms.example.com)。这会破坏通行密钥、CSRF 源匹配、OAuth 重定向、登录重定向、MCP 发现、快照导出、站点地图、robots.txt 以及 JSON-LD 结构化数据。设置 siteUrl 即可一次性修复所有这些问题。
集成会在加载时验证该值:它必须是协议为 http: 或 https: 的有效 URL,并被规范化为源(路径会被去除)。
下面的示例设置公开源:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
当配置中未设置 siteUrl 时,EmDash 会按顺序检查环境变量:先是 EMDASH_SITE_URL,然后是 SITE_URL。这对于在运行时设置公开 URL 的容器部署很有用。
在非环回主机上,如果两个来源都未设置,设置会失败并报 SITE_URL_REQUIRED。这可以防止第一个未经身份验证的设置请求选择后续身份验证邮件中使用的源。部署到 Cloudflare Workers 的生产构建是例外:Cloudflare 只会路由绑定到该 Worker 的主机名的请求,因此设置会记录 https:// 和其运行所在的主机名。同一个构建如果由 wrangler dev、astro preview 或可访问地址上的自托管 workerd 提供服务,则没有这样的保证;请先在那里设置 siteUrl 或 EMDASH_SITE_URL。
在 Cloudflare Workers 上,环境变量回退读取 process.env。使用 nodejs_compat 时,对于兼容性日期为 2025-04-01 及之后的项目,Cloudflare 默认会填充 process.env。固定在更早日期的项目还必须添加 nodejs_compat_populate_process_env。
// wrangler.jsonc
{
"compatibility_date": "2026-02-24",
"compatibility_flags": ["nodejs_compat"],
"vars": { "EMDASH_SITE_URL": "https://cms.example.com" },
}
allowedOrigins
可选。 对于可通过多个主机名访问的部署,通行密钥验证所接受的额外浏览器源。
siteUrl 定义单个规范源。当同一个 EmDash 部署可通过共享同一可注册父域的多个主机名访问时(例如 https://example.com 和 https://preview.example.com),通行密钥验证会拒绝源与 siteUrl 不完全一致的断言——尽管 WebAuthn 允许通行密钥在同一 rpId 下跨子域有效。
通过 astro.config.mjs 中的 allowedOrigins 或 EMDASH_ALLOWED_ORIGINS 环境变量声明额外接受的源。规范的 siteUrl 仍是 rpId 的来源;此处列出的条目会在验证时被接受。两个来源会在运行时合并,因此配置可以声明稳定的源(纳入版本控制、经过代码审查),而环境变量则添加特定于环境的额外项(例如临时的 PR 预览)。
下面的示例在配置中声明一个额外的源:
emdash({
siteUrl: "https://example.com",
allowedOrigins: ["https://preview.example.com"],
})
等效的值也可以来自环境变量:
EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
验证
EmDash 会验证这些值,以防止出现浏览器永远不会遵守的无效配置:
- 每个条目都必须是可解析的
http:或https:URL,不带末尾的点,且主机名中没有空标签。 - 当
allowedOrigins非空时,必须设置siteUrl(来自任一来源),并且它不能是 IP 字面量,也不能是带末尾点的主机名。 - 每个源都必须与
siteUrl是同一主机名,或者是其子域。(WebAuthn 要求rpId是每个源的可注册后缀。)
验证失败时,你会看到带有来源标注的错误,例如 EmDash config error in EMDASH_ALLOWED_ORIGINS: "https://other-site.com" is not a subdomain of siteUrl "https://example.com". Allowed origins must be the same hostname as siteUrl or a subdomain of it.
错误出现的位置取决于这些值的声明位置:
- Astro 启动时:当
config.allowedOrigins和config.siteUrl都来自astro.config.mjs时——代码中的拼写错误会导致构建失败。 - 首次通行密钥验证时:当其中任一值来自
EMDASH_ALLOWED_ORIGINS或EMDASH_SITE_URL时——环境不匹配会在首次验证尝试时表现为 500 错误。
反向代理设置
只有当公开主机被允许时,Astro 才会反映 X-Forwarded-*。请为用户访问的主机名(及协议)配置 security.allowedDomains。在 astro dev 中,请添加匹配的 vite.server.allowedHosts,以便 Vite 接受代理的 Host 标头。
请优先修正 allowedDomains(以及转发的标头);当重建出的 URL 仍然与浏览器源不一致时(典型情况是 TLS 在前端终止,而上游请求仍是 http://),再使用 siteUrl。
在前端有 TLS 的情况下,将开发服务器绑定到环回地址(astro dev --host 127.0.0.1)通常就足够了:代理在本地连接,而 siteUrl 与公开的 HTTPS 源一致。
如果你的代理会写入客户端 IP 标头,请设置 trustedProxyHeaders,这样 EmDash 的速率限制就能使用真实的客户端 IP。没有可信的客户端 IP 时,身份验证速率限制会被跳过,所有公开评论提交共用同一个速率限制桶(所有访客合计每 10 分钟 20 条评论)。
下面的配置为反向代理部署同时设置 allowedDomains、vite.server.allowedHosts 和 siteUrl:
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
security: {
allowedDomains: [
{ hostname: "cms.example.com", protocol: "https" },
{ hostname: "cms.example.com", protocol: "http" },
],
},
vite: {
server: {
allowedHosts: ["cms.example.com"],
},
},
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
}),
],
});
trustedProxyHeaders
可选。 在你所控制的反向代理之后运行时,用于解析客户端 IP 而被信任的标头。供身份验证速率限制(魔法链接、注册、通行密钥、OAuth 设备流程、OAuth 客户端注册)和公开评论端点使用。
在 Cloudflare 上会自动使用附加到请求的 cf 对象——通常无需设置此项。在 nginx、Caddy、Traefik、Fly、Railway 等之后的自托管部署中,请将此项设置为你的代理所写入的标头,这样速率限制就能按真实客户端 IP 统计请求。
下面的示例信任由 nginx、Caddy 或 Traefik 设置的 x-real-ip 标头:
emdash({
database: sqlite({ url: "file:./data.db" }),
trustedProxyHeaders: ["x-real-ip"],
});
标头会按顺序尝试。匹配 *-forwarded-for 的值会被解析为逗号分隔的列表,并使用第一个条目。下面的示例优先使用 Fly.io 的标头,并回退到 x-forwarded-for:
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
当配置中未设置时,EmDash 会读取 EMDASH_TRUSTED_PROXY_HEADERS 环境变量(以逗号分隔)。配置中显式的空数组会覆盖该环境变量。
maxUploadSize
可选。 允许上传的媒体文件的最大大小(字节)。同时适用于直接 multipart 上传和签名 URL 上传。默认值为 52_428_800(50 MB)。下面的示例将上限提高到 100 MB:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
maxUploadSize: 100 * 1024 * 1024, // 100 MB
});
| 值 | 说明 |
|---|---|
number(字节) | 必须是正的有限整数 |
| 省略 | 默认为 50 MB |
超过所配置上限的上传会被拒绝:直接上传路径返回 413 Payload Too Large 响应,签名 URL 路径返回 400 Validation Error。
admin
可选。 替换管理界面中的 EmDash 品牌标识。这些值不会更改公开站点的标题、徽标或站点图标。
emdash({
admin: {
logo: "/images/agency-logo.webp",
siteName: "Agency CMS",
footerLabel: "AgencyX",
favicon: "/favicon.ico",
},
});
| 选项 | 类型 | 说明 |
|---|---|---|
logo | string | 登录页和侧边栏使用的徽标 URL 或路径 |
siteName | string | 显示在侧边栏标题和浏览器标题中的名称 |
footerLabel | string | false | 显示在侧边栏版本号旁的标签。默认为 "EmDash";false 将其隐藏 |
favicon | string | 管理页面的站点图标 URL 或路径 |
updateCheck
可选。 控制管理仪表板中的核心更新通知。默认为 true。
启用后,EmDash 会检查公共 npm 注册表中已发布的 emdash 版本——每天最多一次,并在响应之后延迟执行,因此绝不会拖慢请求——当有更新的版本可用时,在管理仪表板中显示一个可关闭的横幅。关闭横幅会针对该版本将其隐藏;当出现更新的版本时它会再次显示。
横幅所提示的是已公开至少 24 小时的最新稳定版本,因此一个版本会在发布一天后才被提示。若要与包管理器的发布时长策略(例如 pnpm 的 minimumReleaseAge)保持一致,请将 minimumReleaseAge 设为时长字符串("48h"、"7d")或秒数。设为 "7d" 时,横幅会提示至少已发布一周的最新版本:
emdash({
updateCheck: { minimumReleaseAge: "7d" },
})
该检查是对 https://registry.npmjs.org/emdash 的一个普通 GET 请求,不携带任何站点数据。没有出站互联网访问的部署只是永远不会显示横幅。设为 false 可完全禁用该检查:
emdash({
updateCheck: false,
})
横幅仅作提示——更新 EmDash 意味着提升项目中 emdash 包的版本并重新部署,而管理后台无法(也不应)触发这一操作。
toolbar
可选。 控制编辑者工具栏(公开页面上的悬浮胶囊)的交付方式。默认为 "server"。
| 值 | 行为 |
|---|---|
"server"(默认) | 工具栏会在服务端注入到为已通过身份验证的编辑者渲染的每个 HTML 响应中。 |
"client" | 公开 HTML 对每位访客都完全相同。一个很小的引导脚本会在已登录过管理后台的浏览器中显示一个“Edit”胶囊;点击它会验证会话,并携带 _edit 查询参数重新加载页面,该页面始终实时渲染(从不缓存),并带有完整的工具栏。 |
false | 从不渲染工具栏或引导脚本。 |
emdash({
toolbar: "client",
})
当你的公开 HTML 通过共享缓存提供时(Cloudflare Cache Everything / Workers Cache、Fastly、Varnish 等),请使用 "client"。使用服务端注入时,只要匿名访客先预热了缓存,浏览公开站点的编辑者就会收到缓存的匿名版本——不带工具栏——因此工具栏会随缓存状态时有时无。在客户端模式下,不会向可共享的 HTML 中注入任何与会话相关的内容,因此缓存保持完全有效,工具栏也很可靠。
关于 "client" 模式的说明:
- 已登出的访客打开共享的
?_editURL 时会被重定向到规范 URL,因此该参数不会泄露草稿,也不会用页面内容预热额外的缓存条目。 - “已登录”信号是由管理后台设置的、非机密的
localStorage标记;胶囊在进入编辑视图之前会验证真实的会话。 - 引导脚本是一小段内联
<script>。如果你的站点发送了不带'unsafe-inline'的严格Content-Security-Policy,请为它添加哈希——服务端注入的工具栏同样如此。 - EmDash 不会注入任何与会话相关的内容——但如果你自己的模板根据
Astro.locals.user分支(例如为已登录用户显示“Admin”导航链接),这种差异仍会留在你的 HTML 中,并且仍会使缓存碎片化。
无论哪种模式,都可以在浏览器中通过 × 按钮关闭工具栏(按浏览器生效,直到编辑者下次打开管理后台)。预览和编辑模式的响应始终在服务端渲染,并带有 Cache-Control: private, no-store。
数据库适配器
从 emdash/db 导入适配器:
import { sqlite, libsql, postgres } from "emdash/db";
sqlite(config)
使用 Node.js 内置数据库驱动的 SQLite 数据库。下面的示例连接到本地文件:
| 选项 | 类型 | 说明 |
|---|---|---|
url | string | 带 file: 前缀的文件路径 |
sqlite({ url: "file:./data.db" });
libsql(config)
libSQL 数据库。下面的示例连接到远程 libSQL 数据库:
| 选项 | 类型 | 说明 |
|---|---|---|
url | string | 数据库 URL |
authToken | string | 运行时身份验证令牌(本地文件可选) |
migrationAuthTokenEnv | string | 迁移令牌变量名(默认 TURSO_AUTH_TOKEN) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
带连接池的 PostgreSQL 数据库。
| 选项 | 类型 | 说明 |
|---|---|---|
connectionString | string | PostgreSQL 连接 URL |
host | string | 数据库主机 |
port | number | 数据库端口 |
database | string | 数据库名称 |
user | string | 数据库用户 |
password | string | 数据库密码 |
ssl | boolean | 启用 SSL |
pool.min | number | 最小连接池大小(默认:0) |
pool.max | number | 最大连接池大小(默认:10) |
pool.connectionTimeoutMillis | number | 最长连接等待时间(pg 默认:0,无超时) |
pool.idleTimeoutMillis | number | 空闲客户端的存活时间(pg 默认:10,000 毫秒) |
migrationConnectionStringEnv | string | 迁移连接字符串变量名(默认 DATABASE_URL) |
下面的示例使用连接字符串进行连接:
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Cloudflare D1 数据库。从 @emdash-cms/cloudflare 导入。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
binding | string | — | wrangler.jsonc 中的 D1 绑定名称 |
session | string | "disabled" | 读取复制模式:"disabled"、"auto" 或 "primary-first" |
bookmarkCookie | string | "__em_d1_bookmark" | 会话书签的 Cookie 名称 |
coalesce | boolean | false | 在同一事件循环周期内批量处理并发读取;需要 "disabled" 以外的会话模式 |
下面的示例展示了基本绑定以及启用读取副本的绑定:
// Basic
d1({ binding: "DB" });
// With read replicas
d1({ binding: "DB", session: "auto" });
当 session 为 "auto" 或 "primary-first" 时,EmDash 使用 D1 Sessions API 将读取查询路由到就近的副本。已通过身份验证的用户可获得基于书签的“读己所写”一致性。详情请参阅数据库选项 — 读取副本。
hyperdrive(config?)
通过 Cloudflare Hyperdrive 绑定使用 PostgreSQL。从 @emdash-cms/cloudflare 导入此适配器。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
binding | string | "HYPERDRIVE" | 禁用查询缓存的主 Hyperdrive 绑定 |
cachedBinding | string | — | 可选的第二个启用缓存的绑定,用于匿名公开读取 |
preferUncachedAfterWriteMs | number | 60_000 | 设置 cachedBinding 时,内容写入之后公开读取使用主绑定的持续时间 |
migrationConnectionStringEnv | string | 由主绑定派生 | 包含 emdash migrate 所用的 PostgreSQL 直连 URL 的环境变量 |
max | number | 5 | 单个 Worker isolate 到 Hyperdrive 的最大连接数 |
下面的示例让已通过身份验证的请求和写入走未缓存的绑定,而匿名公开读取可以使用已缓存的绑定:
hyperdrive({
binding: "HYPERDRIVE",
cachedBinding: "HYPERDRIVE_CACHED",
preferUncachedAfterWriteMs: 60_000,
});
两个绑定必须指向同一个数据库。请安装 pg 8.16.3 或更高版本,启用 nodejs_compat 兼容性标志,并为部署迁移配置数据库直连 URL。完整的 Worker 和迁移设置请参阅数据库选项:Hyperdrive。
durableObjects(config)
将 CMS 存储在一个由 SQLite 支持的 Durable Object 中。从 @emdash-cms/cloudflare 导入此适配器。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
binding | string | 必填 | EmDashDB 类的 Durable Object 命名空间绑定 |
name | string | "emdash" | 单例对象名称;仅在需要在同一绑定后隔离多个数据库时才修改 |
session | string | "disabled" | "auto" 将匿名读取路由到副本,将写入路由到主库 |
bookmarkCookie | string | "__em_do_bookmark" | 在 "auto" 模式下用于“读己所写”一致性的 Cookie |
durableObjects({ binding: "DB_DO", session: "auto" });
副本路由需要 experimental 和 replica_routing 兼容性标志,以及 wrangler.jsonc 中的 Durable Object 类和迁移条目。
previewDatabase(config)
在 Durable Object 中为每个预览会话创建一个隔离的快照数据库。唯一的选项是必填的 binding 名称:
previewDatabase({ binding: "PREVIEW_DB" });
此适配器用于预览基础设施,而非生产站点的主数据库。
playgroundDatabase(config)
在 Durable Object 中为每个演练场会话创建一个可写且带种子数据的数据库。请将其与 playground 集成选项搭配使用:
playgroundDatabase({ binding: "PLAYGROUND_DB" });
必填的 binding 用于标识演练场的 Durable Object 命名空间。此适配器仅用于一次性演示站点。
存储适配器
从 emdash/astro 导入 local 和 s3。r2 适配器从 @emdash-cms/cloudflare 导入:
import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";
local(config)
本地文件系统存储。下面的示例从本地目录提供上传文件:
| 选项 | 类型 | 说明 |
|---|---|---|
directory | string | 目录路径 |
baseUrl | string | 提供文件访问的基础 URL |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Cloudflare R2 绑定。下面的示例使用带公开 URL 的 R2 绑定:
| 选项 | 类型 | 说明 |
|---|---|---|
binding | string | R2 绑定名称 |
publicUrl | string | 可选的公开 URL |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
与 S3 兼容的存储。所有配置字段都是可选的:s3({...}) 中省略的任何字段,
都会在 Node 进程启动时从对应的 S3_* 环境变量解析。显式值始终优先。
配置值和环境变量值合并之后,endpoint 和 bucket 为必填项。如果设置了任一凭据,则 accessKeyId 和 secretAccessKey 都是必填项。缺少值会导致启动失败,错误码为 MISSING_S3_CONFIG。
前提条件: 请在你的项目中安装 @aws-sdk/client-s3 和 @aws-sdk/s3-request-presigner。
EmDash 核心不捆绑 AWS SDK。详情请参阅
存储选项:S3 兼容存储。
| 选项 | 类型 | 说明 |
|---|---|---|
endpoint | string | S3 端点 URL(S3_ENDPOINT) |
bucket | string | 存储桶名称(S3_BUCKET) |
accessKeyId | string | 访问密钥(S3_ACCESS_KEY_ID) |
secretAccessKey | string | 私有密钥(S3_SECRET_ACCESS_KEY) |
region | string | 区域,默认 "auto"(S3_REGION) |
publicUrl | string | 可选的 CDN URL(S3_PUBLIC_URL) |
下面的示例分别展示:所有字段都从环境解析、混合配置与环境,以及显式传入每个字段:
// All fields from S3_* environment variables (Node container deployments)
s3()
// Mix: CDN from config, rest from environment
s3({ publicUrl: "https://cdn.example.com" })
// All explicit
s3({
endpoint: "https://xxx.r2.cloudflarestorage.com",
bucket: "media",
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
publicUrl: "https://cdn.example.com",
})
运行时环境变量解析是仅限 Node 的功能。在 Cloudflare
Workers 上,密钥和变量通过 fetch 处理程序的 env 参数暴露,
而不是通过 process.env,因此不会读取 S3_* 环境变量。
Workers 部署应使用 r2(config) 适配器,
或向 s3({...}) 传入显式值。详情请参阅
存储选项。
对象缓存适配器
将其中之一传给 objectCache 选项。
kvCache(config)
Cloudflare KV 后端,在所有 isolate 之间共享。从 @emdash-cms/cloudflare 导入。
kvCache({
binding: "CACHE", // KV binding name (required)
defaultTtl: 3600, // entry TTL in seconds (optional, KV minimum 60)
revalidate: 1000, // cross-isolate staleness window in ms (optional)
timeout: 2000, // per-op timeout in ms before a miss (optional, 0 disables)
keyPrefix: "em", // cache key prefix (optional)
})
memoryCache(config?)
适用于 Node.js 和开发环境的进程内后端。从 emdash/astro 导入。
memoryCache({
defaultTtl: 3600, // entry TTL in seconds (optional)
revalidate: 1000, // staleness window in ms (optional)
maxEntries: 1000, // max cached keys before eviction (optional)
keyPrefix: "em", // cache key prefix (optional)
})
设置与行为请参阅对象缓存。
身份验证与沙箱适配器
这些适配器为 auth 和 sandboxRunner 集成选项返回值。
access(config)
用 Cloudflare Access 身份验证替换内置的通行密钥登录。从 @emdash-cms/cloudflare 导入,并将结果传给 auth:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audienceEnvVar: "CF_ACCESS_AUDIENCE",
}),
});
teamDomain 为必填项。适配器可以从 audience 或 audienceEnvVar 所指定的变量中读取应用程序 audience;它还接受 autoProvision、defaultRole、syncRoles 和 roleMapping。这些选项的默认值和角色行为记录在 auth 选项中。
sandbox()
选择 Cloudflare Worker Loader 作为插件沙箱运行器。从 @emdash-cms/cloudflare 导入,并将其返回值传给 sandboxRunner:
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
});
站点还需要一个 Worker Loader 绑定和插件桥接入口。有关这些部署设置,请参阅插件沙箱:Cloudflare Workers。
媒体提供者适配器
将媒体提供者描述符传给 mediaProviders。两个内置的 Cloudflare 提供者都从 @emdash-cms/cloudflare 导入。
下面每个 *EnvVar 选项都指定一个环境变量名。提供者按以下顺序读取:先是同名的 Cloudflare Workers 绑定,然后是 Node 适配器上的 process.env。对应的直接选项(accountId、accountHash、apiToken)始终优先于这两者。
cloudflareImages(config)
添加 Cloudflare Images,用于浏览、上传、删除和分发图像资源。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
accountId | string | 来自 CF_ACCOUNT_ID | Cloudflare 账号 ID |
accountIdEnvVar | string | "CF_ACCOUNT_ID" | 省略 accountId 时使用的变量 |
accountHash | string | 来自 CF_IMAGES_ACCOUNT_HASH | 分发 URL 中使用的账号哈希 |
accountHashEnvVar | string | "CF_IMAGES_ACCOUNT_HASH" | 省略 accountHash 时使用的变量 |
apiToken | string | 来自 CF_IMAGES_TOKEN | 具有 Cloudflare Images 读取和编辑权限的令牌 |
apiTokenEnvVar | string | "CF_IMAGES_TOKEN" | 省略 apiToken 时使用的变量 |
deliveryDomain | string | imagedelivery.net | 自定义图像分发主机名 |
defaultVariant | string | "public" | 用于显示的图像变体 |
mediaProviders: [cloudflareImages({ defaultVariant: "public" })];
cloudflareStream(config)
添加 Cloudflare Stream,用于浏览、搜索、上传、删除和播放视频资源。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
accountId | string | 来自 CF_ACCOUNT_ID | Cloudflare 账号 ID |
accountIdEnvVar | string | "CF_ACCOUNT_ID" | 省略 accountId 时使用的变量 |
apiToken | string | 来自 CF_STREAM_TOKEN | 具有 Cloudflare Stream 读取和编辑权限的令牌 |
apiTokenEnvVar | string | "CF_STREAM_TOKEN" | 省略 apiToken 时使用的变量 |
customerSubdomain | string | Cloudflare 默认值 | 自定义 Stream 分发主机名 |
controls | boolean | true | 显示播放器控件 |
autoplay | boolean | false | 自动开始播放 |
loop | boolean | false | 循环播放 |
muted | boolean | false,启用自动播放时为 true | 静音播放 |
mediaProviders: [cloudflareStream({ controls: true })];
有关所需的绑定和渲染组件,请参阅媒体库:媒体提供者。
实时集合
在 src/live.config.ts 中配置 EmDash 加载器:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
加载器选项
emdashLoader() 函数不接受参数:
emdashLoader();
环境变量
EmDash 会识别以下环境变量:
| 变量 | 说明 |
|---|---|
EMDASH_SITE_URL | 面向浏览器的公开源(回退到 SITE_URL) |
EMDASH_ALLOWED_ORIGINS | 通行密钥验证所接受的额外源的逗号分隔列表(多子域部署)。 |
EMDASH_DATABASE_URL | 覆盖数据库 URL |
EMDASH_ENCRYPTION_KEY | 用于静态加密插件密钥的密钥。由运维人员提供——绝不存储在数据库中。 |
EMDASH_PREVIEW_SECRET | 预览 HMAC 密钥的可选覆盖。未设置时,会生成每个站点稳定的值并存储在数据库中。 |
EMDASH_IP_SALT | 评论者 IP 哈希盐的可选覆盖。未设置时,会生成每个站点稳定的值并存储在数据库中。 |
EMDASH_AUTH_SECRET | 旧版。如已设置,则用作 IP 盐的来源;现有安装应保留它,以便在升级后保持评论者 IP 哈希稳定。 |
EMDASH_TURNSTILE_SECRET_KEY | Cloudflare Turnstile 密钥(回退到 TURNSTILE_SECRET_KEY)。设置后,评论提交必须包含有效的 Turnstile 令牌——请与 <CommentForm> 上的 turnstileSiteKey 属性搭配使用。 |
EMDASH_URL | 用于架构同步的远程 EmDash URL |
使用以下命令生成加密密钥:
npx emdash secrets generate
package.json 配置
模板和站点可以在 package.json 的 emdash 键下声明可选的元数据:
{
"emdash": {
"label": "My Blog Template",
"schema": ".emdash/schema.sql",
"seed": ".emdash/seed.json"
}
}
| 选项 | 说明 |
|---|---|
label | 用于显示的模板名称 |
schema | emdash init 读取的可选 SQL 架构 |
seed | 种子 JSON 文件的路径 |
TypeScript 配置
在本地开发期间,Astro 集成会在项目根目录生成 emdash-env.d.ts,并在架构变更后刷新它。该文件扩充了 emdash 模块,因此标准的 getEmDashCollection() 和 getEmDashEntry() 导入无需路径别名即可推断本地集合的字段。
单独的 emdash types 命令会从正在运行的本地或远程实例获取架构,默认写入 .emdash/types.ts。仅当应用代码直接导入该独立输出时,才需要添加别名:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
使用以下命令生成远程架构的独立类型:
npx emdash types