2026年9月28日,EmDash 1.0版本正式发布。
EmDash是由Cloudflare开发的基于Astro的开源CMS,其管理界面可以直接嵌入到Astro网站中,并运行在Cloudflare Workers + D1 + R2之上。
可以说它是WordPress的Astro版本。
在之前的文章中提到的从 microCMS 迁移过来的内部博客"ゆるろぐ"一直停留在 6 月发布的 0.22 版本,所以趁这个机会一口气升级到了最新的 1.2.0 版本。
1.0 版本的亮点在于稳定性和升级的安全性、在沙箱中运行的插件,以及为代理提供的 MCP 服务器。
简单来说,迁移在一天内就完成了,但陷阱很多。
"既然已经升到1.0了,应该很稳定了"如果抱着这样轻松的想法开始,你不会在EmDash本体卡住,而是会在周边的依赖关系上遇到麻烦。
前提:0.22 → 1.2 必须手动升级
1.2 中引入了一个官方升级 CLI,名为upgrade-emdash。
它可以更新依赖项并生成站点特定的UPGRADE.md,但由于它仅支持 0.35 及以后的版本,所以无法从 0.22 使用。
在 0.22 到 1.2 之间,有 47 个数据库迁移,其中也包含一些相当破坏性的更改。
"并不是简单地运行 npm install emdash@latest 就能解决的事情。"
1. 获取备份,并在本地复制生产 DB
npx wrangler d1 export <database-name> --remote --output=backups/pre-1.2.sql
将其流向本地 D1,然后用与生产环境相同的数据尝试迁移。
2. 更新依赖项
包 | 迁移前 | 迁移后 |
|---|---|---|
emdash / @emdash-cms/cloudflare / @emdash-cms/admin | 0.22.0 | 1.2.0 |
@emdash-cms/plugin-forms | 0.1.1 | 0.2.9 |
astro | 6.1.2 | 6.4.8 |
wrangler | 4.80 | 4.148 |
wrangler 需要在 emdash 1.2 的同级依赖要求(4.99 以上)上升级,因此我们在这个时点移除了 0.22 时代的临时补丁。@astrojs/cloudflare满足同级依赖要求,所以在这个时点保持在 13.1.6。这成为了下一个陷阱的原因。
- OAuth 路由的 patch-package:为了规避 0.22 的 bug 而应用,本体已修复
- 管理界面的 CSS 注入:0.22 的管理界面针对 UI 库 kumo 2.4 的 CSS,但 lockfile 引入了 2.6,导致登录和发布强调按钮呈现为「透明背景白字」而看不见。1.2 版本本体已修复
3. dev 无法启动(陷阱:miniflare 版本不匹配)
升级依赖后执行npm run dev无法启动的原因是miniflare 版本不匹配。
wrangler 4.148 使用 miniflare 5,会将本地 D1 状态(.wrangler/state/v3/下)的_cf_ALARM表更新为 3 列。
而@astrojs/cloudflare 13.1.6 内置的 miniflare 是 4,由于列数不匹配无法读取本地状态。
解决方法是同时升级保持不变的@astrojs/cloudflare,使 miniflare 版本与 wrangler 对齐。只需记住「wrangler 和适配器的 miniflare 版本需对齐」,下次就能一次解决。
在 0.22 版本中,emdash dev 命令已被弃用,因此我们将 dev 脚本改回 astro dev。迁移和类型生成在 astro dev 启动时运行。
4. Staging 中的菜单消失了(陷阱:seed.json 中的 menus)
dev 启动后,90 条迁移运行成功,上传到 staging 后打开管理界面时,发现 日语全局导航为空。
原因是 seed/seed.json 中的 menus 和 widgetAreas。
EmDash 的种子处理在检测到同名菜单或小部件区域时,会删除所有项目后以空状态重新创建。
seed.json 中作为模板保留了 "items": [] 的菜单定义,因此每次运行种子时菜单都会被重置。
解决方案是删除 menus 和 widgetAreas 键。
菜单内容应该在管理界面中管理,没有理由将其保留在种子文件中。
5. 英文文章的链接变成 /en/posts/en/slug(陷阱:entry.id 的语言前缀)
这是版本 1.2 的规范变更。
在多语言网站中,getEmDashCollection() 返回的 entry.id 会加上语言前缀。英文文章的 entry.id 是 en/my-post。
由于 Yuru Log 从 entry.id 组建文章 URL,英文文章的链接变成了 /en/posts/en/my-post(重复了)。
我们将列表、精选、搜索结果、RSS 和 canonical 中所有组建 URL 的位置都替换为 entry.data.slug。
由于 entry.data.slug 的类型是 string | null,接收端的类型也需要相应调整。
可以这样记住各自的角色:entry.id 是键、entry.data.slug 是 URL、entry.data.id 是数据库中的 ULID(用于 API 调用)。
6. 每分钟出现错误(陷阱:Cron Trigger 和 Worker 入口)
在 1.x 中,预定发布和插件的定期处理由 Worker 的 scheduled() 处理程序驱动。
Cloudflare 没有内部计时器,所以如果没有 Cron Trigger,预定发布即使到了发布时间也不会发布。
因此wrangler.jsonc添加了 Cron Trigger。
"triggers": { "crons": ["* * * * *"] }接下来每分钟都会出现 "Handler does not export a scheduled() function" 的错误。
这是因为 Worker 的入口点scheduled()没有被导出。
解决办法是用官方的入口文件替换 src/worker.ts。
export { default, PluginBridge } from "@emdash-cms/cloudflare/worker";现在除了 fetch 和 scheduled,还配备了用于沙盒型插件的 PluginBridge。
亲身体验一下
升级到 1.2 的优点。
- 编辑器经过重新设计,使用起来更加便捷。斜杠菜单已分组,工具栏现在会跟随光标。在移动设备上也可以使用。
- 内置了视频块。之前是用自定义插件处理视频嵌入的,打算之后用这个功能替换。
- 0.22 时代的临时应对方案全部移除了。不再需要 patch-package 和 CSS 注入。
需要关注的问题。
- 从 1.0 到 1.2 只用了 10 天,更新速度很快。今后可能需要在分级版本更新后都在预发布环境进行验证。
总结
- 0.22 → 1.2 需要手动迁移,
upgrade-emdashCLI 仅支持 0.35 及更高版本。 - wrangler 和适配器的 miniflare 版本需要匹配→版本不匹配时开发环境无法启动
- seed.json 中不保留
menus/widgetAreas的空模板 → 每次播种时消失 - 多语言网站的
entry.id带有区域前缀→URL来自entry.data.slug - 如果要添加 Cron Trigger,则将 Worker 入口点设为
@emdash-cms/cloudflare/worker
这些问题看似只需要修改几行代码就能解决,但逐一排查的过程往往比预期花费更多时间。
不过,在0.22版本时应用的补丁和CSS应急处理方案现在全部可以去掉了,这也证明了向正式发布迈进的过程中细微的bug在不断被修复。
半年前还是"试验性地尝试了替换"的态度,但经过1.0版本的发布,只要考虑到跟进更新的工作成本,我认为这个CMS已经成为一个足够值得在实际运营中认真考虑的选择了。
从DTP跨越到Web世界,不知不觉中已掌握标签、前端开发、创意指导、无障碍设计等各项技能的"技术高手"。从Liberogic创业初期就活跃至今,如今是公司内部的"活百科"。最近沉迷于"能否在无障碍适配上更多依赖AI?"这样的思考,正在探索借助AI提示词提高效率的方法。技术实力和思维方式都还在不断进化中
二俣 歩
IAAP 认证网络无障碍专家 (WAS) / 标记语言工程师 / 前端工程师 / 网络总监