Topics

EmDash正式版已发布。从0.22升级到1.2的体验

  • column

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-emdash CLI 仅支持 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) / 标记语言工程师 / 前端工程师 / 网络总监

查看本员工的文章

安心的团队体制和迅速的反应能力是我们的优势

Liberogic 拥有经验丰富的员工团队,积极推进项目,因此获得了客户的高度评价。
我们会妥善分配项目经理和总监,确保整个项目顺利进行。 通过避免不必要的全职投入导致的成本增加,并采用适当配置人力资源的方式,从把握业务内容到估价的制作和提交速度都赢得了良好的口碑。

* 本公司不积极开展SES驻场工作等业务,敬请谅解。

Slack、Teams、Redmine、Backlog、Asana、Jira、Notion、Google Workspace、Zoom、Webex 等,您可以使用几乎所有主要的项目管理工具和沟通协作工具。

请咨询我们的网站相关问题。

案例分析