再次阴沟翻船:在 Cloudflare 上搭建 Payload CMS,又连踩五个坑

前几天编辑跟我说,拜拜 CMS 的 MCP 接口 404。我本以为是个路径问题,结果一查,竟然前后修了五个独立的问题,每一个都盖在下一个上面。技术栈是 Payload CMS + OpenNext + Cloudflare Workers,按踩坑顺序记录,希望能帮到同样组合的人。

1. OpenNext 的 esbuild 构建失败

既然 404,那就重新构建,结果 Next.js 构建过了,但 OpenNext 打 Worker 包时报:

No matching export in "shims/env.js" for import "default"
  payload/dist/bin/loadEnv.js

Payload 的 loadEnv.js 用默认导入引用 @next/env,然后 OpenNext 会替换成自己的 shim——那个 shim 只有具名导出、没有 default,于是 esbuild 直接报错中断。

更麻烦的是这个失败是静默的:构建挂了,线上继续服务上一次成功的构建,旧站照常跑,没有任何告警。

修法:采用 Payload 官方 issue 里的修法(只是还没发版),用 pnpm patch 把那行改成命名空间导入 import * as。兴许你看到这篇文章时,官方已经修好这个问题。

OpenNext 部署常常静默失败,每次部署后一定要验证线上端点,不要假设它一定能成功。

2. deploy 阶段卡在 R2 增量缓存

构建通过,轮到 deploy 又挂:Failed to provision remote R2 bucket "site-cache"

open-next.config 之前配了 R2 增量缓存(ISR),deploy 时要去 provision 那个桶,反复失败。我确定那个桶存在,我也有权限(自己的桶),token 也更新,但就是修不好。

好在这是个纯 CMS,路由几乎全是动态(admin / API),ISR 基本没用。所以移除缓存配置、回到 OpenNext 默认,deploy 通过。

纯动态站点别画蛇添足配 ISR,少一个外部依赖就少一个失败点。

3. 部署成功,但运行时 500

终于部署成功,结果所有动态路由 500,静态首页正常。wrangler tail 抓到:

Error: [unenv] process.report.getReport is not implemented yet!

追下去:AI 不知道参考哪里的代码,给 Payload 装了 sharp 依赖。这个举动本身没问题,CMS 装 sharp 自动优化用户上传的图片很合理。但是这样启动时就会加载 sharp,sharp 依赖 detect-libc,后者在 Linux 下会调 process.report.getReport()——而 Workers 的 Node 兼容层 unenv 没实现这个 API,直接抛错。

本质上是 sharp 是原生模块,在 workerd 上根本跑不起来。之前只是依赖版本旧、没触发到这条调用而已。直接不让 Payload 用 sharp 即可(Payload 官方的 Cloudflare 模板也不用),需要图片压缩、裁剪可以直接用 Cloudflare Image 服务(稍微有点贵,如果你有一些流量的话)。

workerd 不是 Node。任何原生模块(sharp、better-sqlite3……)都跑不了,能避则避。

4. 编辑页面打不开:版本不一致

终于搞定前面,打开 /admin 准备编辑,可是白屏,控制台报:

useUploadHandlers must be used within UploadHandlersProvider

这是 Payload 的一个经典坑:monorepo 里 payload 和各 @payloadcms/*(ui / next / storage 等)版本必须完全一致,否则上传 handler 的 hook 和 Provider 来自不同版本的包,React context 对不上就报这个错。

我这边 storage-r2 还停在 3.84.1,其余都升到了 3.85.1。本想往上对齐,结果新版的 storage-r2 客户端把一堆服务端代码拽进了客户端包,构建又是一串 node: / 裸内置模块报错(上游回归)。于是只能反向操作,把全部 @payloadcms/*payload 精确锁回 3.84.1——也就是升级前那套在生产稳定跑过的版本。

Payload 全家桶版本要锁死、对齐,别让 ^ 把它们漂成不同小版本。

5. workerd 的运行时细节

最后还有些 workerd 特有的运行时细节。日志里一直刷 Failed to publish diagnostics channel message——检查半天无果。只好找来一个能跑的 Payload 实例作对比,一项一项比到最后,发现 compatibility_date 有点旧(差小半年),OpenNext 一直 warn 让更新。(实际上 workerd 的 cookie / crypto / Node API 行为都和这个日期挂钩)。

对齐到较新的日期,问题解决。

这之后,终于没更多的问题出现。

Cloudflare Worker 的兼容性日期,能新就尽量新,应该加入项目维护定期更新。

总结:拥抱 AI 的随机性,增加人为的确定性

AI 跟人不一样,人的记忆力更大,记忆时间更长。掌握到一个最佳时间之后,会反复使用优化迭代这个最佳实践。AI 的知识库比较旧,很多知识需要现场学习,如何实践比较随机,多半决定于它连网搜索的结果。于是,一次成一次败也是常见情况。那么我们就要:

  1. 留一个已知能跑的同栈参照项目。 当你怀疑配置、版本、compat flag 出问题时,对着一个跑得通的项目逐项 diff,比对着文档猜快得多。我后面几个问题都是这么定位的。

  2. 积累最佳实践,落实文档,搭建项目间的共享机制,提升后面的效率。

  3. 逐层剥洋葱。 不着急,慢慢来,现在有 AI,大部分 bug 修复痘不难,耐心一点总能修好。

希望能帮到同样在 Cloudflare 上折腾 Payload 的人。今年的目标是提供各种最佳实践,让大家的 AI 更好用。欢迎关注我和分享我们的博客。有问题欢迎留言讨论。

相关文章

觉得文章有帮助?

如果我的分享对你有所启发,欢迎通过赞助来支持我持续创作。

❤️ 赞助我

评论