Vercel 自家仪表盘跑 next dev 从 21.5GB 内存掉到 2GB——Next.js 16.3 把开发内存和 CI 构建一起重写了

Vercel 自家仪表盘跑 next dev,编译完 50 个路由,内存从 21.5GB 掉到 2GB。nextjs.org 从 4.6GB 掉到 840MB。升级 Next.js 16.3,你一行业务代码都不用改。

这不是营销文案里的圆整数,是 Vercel 官方博客给出的可复现基准。用的是 Turbopack 在这三个月里做的两件事:磁盘缓存 + 内存回收,而且默认开启。

先说清楚一件事:16.3 里真正让所有存量项目受益的,不是那个被标题党反复刷的 Instant Navigations,而是默认生效的开发内存与构建缓存。前者要手动开两个 flag,后者是你 npm i 完就白拿的。

为什么 next dev 一直是内存黑洞

Turbopack 的核心设计是增量编译:缓存做过的工作,避免重复编译没改的文件。这个设计有个刻意的取舍——用内存换 CPU。缓存越多,重建越省,但内存就越涨。

问题在于,今天的开发时不只是跑一个编译器。IDE、类型检查器、Linter,现在还要再挂一个 coding agent,每一块都在抢系统内存。长期挂在 next dev 上的大项目,内存曲线是一路往上爬的,爬着爬着就撞上 Node 的 4GB 堆上限,然后就是那句熟悉的:

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

这不是代码 bug,是架构层面的账没算平。16.3 补的就是这一刀。

关键招数:把缓存从内存挪到磁盘,再主动回收

16.3 的两把刀是配套的:

  • 磁盘缓存(Disk Caching):Rust 打包器不再把构建产物永久攥在内存里,而是写到文件系统,用到时再读回来。这个能力 16.1 就在 dev 里试了,16.3 正式成为默认。
  • 内存回收(Memory Eviction):自动把不用的数据从内存里踢出去,防止 dev 会话无限膨胀。

内存回收有个前置条件——开发文件系统缓存必须是开着的。16.3 里这两个选项默认都开着(内存回收的默认值是 auto)。如果你正在排查缓存或 dev 性能问题,可以手动关掉它:

// next.config.js
const nextConfig = {
  experimental: {
    turbopackMemoryEviction: false, // 默认是 'auto'
  },
};

官方给的实测数据(都是编译完 50 个路由后):

项目 升级前 升级后 降幅
vercel.com 仪表盘 21.5 GB 2 GB ~90%
nextjs.org 4,600 MB 840 MB ~82%

The Register 也报道了一位早期使用者,内存从约 4GB 掉到 1.5GB,评价是「总算回到正常水平了」。

同一份缓存,顺手把 CI 构建也提了 5.5 倍

磁盘缓存不只是给 dev 用的。16.3 里它同时接管了 next build:重复构建时,没变过的产物直接从缓存读,不再重新编译。

官方给出的 Turbopack 构建耗时对比:

  • nextjs.org:冷构建 21s → 缓存 9.2s(约 2.3×)
  • vercel.com/home:66s → 46s(约 1.4×)
  • vercel.com/geist:30s → 5.5s(5.5×)

对天天推 PR、每次都要跑一遍 build 的团队来说,这个数字比内存那 90% 更值钱。文档里还有更猛的曲线:一个 19,000 模块的生产构建,从 40 秒掉到 1.6 秒左右。

这里有个可落地的下一步:不用改任何配置,先 npm i next@latest 升到 16.3,跑一次构建、再跑一次,对比第二次的耗时和 next dev 驻留 1 小时后的内存曲线。这两个数字会直接告诉你有没有必要继续往下接 Instant Navigations。

零代码改动白拿的另外三样

除了内存和构建缓存,16.3 还有几个「升级就有」的收益,全部不需要动业务代码:

  1. SSR 改用原生 Node.js 流。App Router 的渲染层把 Web Streams 换成了 Node.js 原生流,省掉一道转换。负载测试下,同样的机器能多扛 22% 的请求。副作用是有个明确信号——边缘运行时(Edge Runtime)已经正式废弃,官方在往 Node 技术栈收拢,还在用 edge runtime 的项目要开始规划迁移了。
  2. TypeScript 7 支持。TypeScript 7 是微软用原生(Go)重写的移植版,号称类型检查快约 10 倍。升级本地依赖就能在 next build 时用上:
    pnpm add -D typescript@^7
  3. 给 AI agent 的版本化文档。跑 next dev 时,Next.js 会在项目里维护一个版本对齐的 AGENTS.md 块,把 agent 指向 node_modules 里那版文档。agent 读到的是你实际安装的版本,而不是可能过期的训练数据——这个改动很小,但对用 Claude Code / Cursor 写 Next.js 的团队,能直接减少「agent 编了一个不存在的 API」这类返工。

Instant Navigations:真正的主角,但要按路由逐个接

前面都是白拿的。16.3 唯一需要你主动接的,是 Instant Navigations——让服务端渲染的应用,拥有客户端 SPA 那种「点下去立刻有反应」的手感,同时不放弃服务端渲染的收益。

它建立在去年 11 月的 use cache 指令之上,用两个 flag 开启:

// next.config.ts
const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true, // 依赖 cacheComponents,单独开会直接报配置错误
};

机制可以这样理解:服务端组件把可静态化的部分预渲染成一个「外壳(shell)」,导航时外壳立刻渲染出来,动态内容在背后继续流。部分预取(Partial Prefetching)把多个小预取请求合并成每条路由一份可复用外壳——官方举例是侧边栏 20 个链接,从 20 次请求收成 1 次缓存外壳请求。

配套还给了四件工具:Instant Insights(自动揪出所有不是「即时」的导航)、Navigation Inspector(在 shell 状态暂停,看用户实际拿到什么)、Playwright 的 instant() 辅助函数(写成回归测试,防止某次重构把导航搞慢)、以及更细的增量静态再生成。

但别一次性全开。官方反馈里明确列了几个坑:

  • 静态导出(static export)和 Partial Prefetching 不兼容;
  • 全局 styled-jsx 样式会在路由之间泄漏;
  • 在 SST 上自托管 + cacheComponents,可能直接把服务端渲染搞坏。

Appwrite 给的建议是:默认收益现在就升级,但 Instant Navigations 一条路由一条路由地接。好在官方提供了一个 next-cache-components-optimizer 技能,可以让 coding agent 帮你逐条路由写失败测试、改代码、验证——v0 团队就是这么把首页、聊天、设置页全部做成即时导航的。

结论

Next.js 16.3 这次更新,本质是把「服务端渲染的应用能不能像 SPA 一样跟手」和「dev 到底要不要吃掉你 21GB 内存」这两件积怨已久的事,一起结了。

对绝大多数项目,正确的顺序是:

  1. 先升级拿白拿的——内存 -90%、CI 构建最快 5.5×、SSR 多扛 22% 请求,零代码改动;
  2. 试试实验性的 Rust React Compiler——它直接在 Turbopack 里跑,不再经过 Babel,官方称从启动到页面完全加载最多快 46%;
  3. 最后再一条路由一条路由地接 Instant Navigations,用 instant() 写测试兜底。

先把那条内存曲线和第二次构建的耗时测出来。这两个数字,就是你决定要不要继续往下走的全部依据。

评论区

0 条评论

登录后可评论。

阿监·前端工程化 1007 阅读