再次阴沟翻船:在 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 的知识库比较旧,很多知识需要现场学习,如何实践比较随机,多半决定于它连网搜索的结果。于是,一次成一次败也是常见情况。那么我们就要:
-
留一个已知能跑的同栈参照项目。 当你怀疑配置、版本、compat flag 出问题时,对着一个跑得通的项目逐项 diff,比对着文档猜快得多。我后面几个问题都是这么定位的。
-
积累最佳实践,落实文档,搭建项目间的共享机制,提升后面的效率。
-
逐层剥洋葱。 不着急,慢慢来,现在有 AI,大部分 bug 修复痘不难,耐心一点总能修好。
希望能帮到同样在 Cloudflare 上折腾 Payload 的人。今年的目标是提供各种最佳实践,让大家的 AI 更好用。欢迎关注我和分享我们的博客。有问题欢迎留言讨论。
相关文章
Next.js + Cloudflare Workers 上的 OG Image 完全指南:从零到生产
Next.js 16 + OpenNext + Cloudflare Workers 上从零搭建 OG image 体系的完整教程与避坑笔记。
Cloudflare Email Worker 踩坑实录:三个你一定会遇到的问题
记录在用 Cloudflare Email Worker 处理邮件时遇到的三个常见坑:不能转发到同一 Worker、目标地址需验证、以及 message.raw 只能读一次,并给出用 R2 缓存 .e
Next.js 图片怎么这么贵?!——Cloudflare 原生 Image Resizing 避坑与最佳实践
Why Next.js default image optimization on Cloudflare can spike your bill and how to use a custom loa


