跳到正文
Fangfang Chen
← 返回文章列表

作者: Fangfang Chen · 工程 · 2026年11月4日

在 Cloudflare 上部署 Next.js,不用 Vercel

从 Pages 到 Workers、bundle 体积核算,以及真正重要的限制。


第一次 Cloudflare 部署失败,不是因为代码太慢,而是因为构建体积用错了单位。

VieauQc 一开始用 next-on-pages 部署在 Cloudflare Pages 上。Pages Functions 的构建达到 27,255,196 字节——约 25.99 MiB——而原始字节限制是 25 MiB。同一个构建 gzip 后只有 6.43 MiB。在 Workers 上,压缩后的大小才是关键数字。这个差异促使我们在 2026 年 7 月 16 日从 Pages 迁移到 Workers + OpenNext。这不是离开 Vercel;Vercel 从来不是生产环境。

第一次 OpenNext 构建是 gzip 后 5.47 MiB。到 9 月中旬,它变成 9.42 MiB,也就是旧版 10 MB 付费上限的 94.2%。然后 Cloudflare 改了规则:从 2026 年 9 月 4 日起,只计算未压缩大小,限制为 64 MiB,并要求一秒内启动。实测启动时间是 27 ms。限制变了,但没有消失。

到 10 月 12 日,部署体积是 50.57 MiB,达到新限制的 79%。拆分比总数更有用:大约 12.93 MiB 是每条路由重复的 client-reference manifest,21.35 MiB 是共享 chunk,5.77 MiB 是页面代码,2.25 MiB 是 Next runtime,还有约 9.8 MiB 是指南正文,藏在 chunk 里。

这次排查得出两个教训。

第一,动态 import() 不会把代码从 Worker bundle 里移除。它控制代码什么时候运行,不控制它是否被打包。指南模块早就通过动态 import 加载;它们仍然计入体积。

第二,真正持久的收益来自搬走数据和修正打包,而不是增加框架功能。对话和词汇表数据在 2026 年 5 月搬到 R2。新闻正文在 8 月 8 日跟上。语法和阅读内容在 9 月 2 日搬走。指南正文仍在 Worker bundle 里,所以它们依然是最大的一块。

最便宜的打包修复只有一行:在 wrangler.jsonc 里加 "minify": true。Wrangler 之前会把已经压缩过的 OpenNext 输出重新打包,却不做 minify,这多出了约 7.3 MiB。10 月 13 日,部署从 50.71 MiB 降到 43.36 MiB。到 11 月 4 日,日志显示 48.405 MiB,是 64 MiB 限制的 75.6%。

还有一些运维规则和 bundle 一样重要:

  • 环境变量按构建时和运行时分开。一个过期的 worktree 曾把开发用的 Clerk key 内联进生产环境,导致每个页面都返回 HTTP 500。现在如果缺少必需的构建时变量,或者出现 pk_test_ 开头的 key,构建会直接失败。
  • R2 内容与代码分开同步。一次部署不会推送音频或文章正文。如果漏了同步,页面可能显示空白正文,而且没有报错。这种情况在 2026 年 8 月 13 日、10 月 29 日和 11 月 3 日都发生过。
  • 部署是从 main 手动执行的。git push 不会部署。

这些都不是 Next.js 特有的问题。把一个内容密集的应用放到边缘运行时,本来就是这些普通工作:决定什么是代码、什么是数据、什么是构建时生成、什么是运行时获取。现在部署很无聊,而这正是重点。