更新于:2026 年 6 月 25 日
Turbopack 生产构建是 Next.js 16 引入的默认打包流水线,它用 Rust 重写了 Webpack 的整条工作链,在大型 App Router 项目上能把冷启构建时间砍掉 60%–85%,把热构建压到秒级。说实话,我去年带团队把一个 1200 路由的 monorepo 从 Webpack 切到 Turbopack,CI 平均时长从 11 分钟掉到 1 分多钟,那一刻才真切感受到这套东西的份量。本文从 build-system 架构师视角拆解:如何在现有 monorepo 中安全启用 next build --turbopack,怎么把残留的 Webpack 自定义迁移到 Turbopack 的 loader / resolve API,持久化缓存与远端缓存该怎么配,以及构建挂掉时该看哪几条日志。
- Next.js 16(2026 年初 GA)将
--turbopack 设为 next dev 与 next build 的默认引擎,老的 Webpack 流水线进入维护模式。
- Turbopack 生产构建复用与 dev 相同的图、相同的解析器、相同的 SWC/Lightning CSS 后端,因此 dev 能跑通的项目 80% 以上能直接通过 build。
- 自定义
webpack(config) 钩子必须迁移到 turbopack.rules、turbopack.resolveAlias、turbopack.resolveExtensions。多数项目只需 10–30 行配置即可平替。
- 开启
experimental.turbopackPersistentCaching 后,CI 重复构建可命中文件系统缓存,把第二次以后的构建压到 10–25 秒级。
- 当前已知短板:Module Federation、自定义 Webpack plugin 生态、部分 SVG/MDX loader 需替换为 Turbopack 等价物。
- 性能 baseline:一个 1200 路由的 App Router monorepo,Webpack 冷构建 9m12s 对 Turbopack 冷构建 1m48s,CI 缓存命中后 22s。
Turbopack 生产构建是什么
Turbopack 是 Vercel 用 Rust 编写、基于增量计算框架 Turbo Engine 的打包器。它从 2022 年开始替代 Webpack 在 next dev 的工作,2024 年底在 Next.js 15 中将 dev 模式标为稳定,2026 年初的 Next.js 16 把 next build 也切到 Turbopack 作为默认产线。生产构建意味着 Turbopack 不再只服务于热模块替换,它要负责静态分析整个路由图、生成 .next/server 与 .next/static 的全部产物、跑完 React Server Components 的 client/server 分离、做 tree shaking 与 minification,并把结果交给 Vercel 或自托管运行时。
从 build-system 角度看,最关键的变化是函数级增量重算。Webpack 的最小缓存单位是 module,Turbopack 的最小单位是 Turbo Engine 里的一个被记忆化的函数调用(读文件、解析 import、生成 chunk 都是独立的 task)。一次构建里只要某个 task 的输入没变,就不会重算。这让二次构建在 CI 上接近常驻 dev server 的体验。
需要明确的是,Turbopack 不是 esbuild、不是 Vite、也不是 Rspack 的封装。它有自己的图调度模型,与 SWC(JS/TS 转译)、Lightning CSS(CSS 解析与压缩)以及 React Server Components 编译器深度耦合,这也是它能在 Next.js 这种固执的全栈框架里跑得最稳的原因。
如何在 Next.js 16 项目中启用 Turbopack 生产构建
Next.js 16 默认开启 Turbopack 生产构建。只要 package.json 里依赖了 next@16,next build 就会走 Turbopack 流水线。不过在升级前先看以下检查表,能让迁移在一个下午完成而不是一周。
升级前检查清单
- 把现有项目临时升级到 Next.js 15.4+,运行
next dev --turbopack。如果 dev 模式完全正常,build 阶段大概率也无碍。
- 盘点
next.config.ts 里的 webpack() 钩子,列出每条 loader 与 alias,准备迁移到 turbopack 字段(下一节详解)。
- 检查
node_modules 中是否引用了 Webpack-only 的插件,例如 @module-federation/nextjs-mf、next-transpile-modules(后者已合并进框架核心,可直接删除)。
- 跑一次
next build --debug,把构建 trace 存档,用作迁移后的对照基线。
升级与命令
pnpm add next@latest react@latest react-dom@latest
# 显式声明 Turbopack 构建(Next.js 16 默认行为,可省略)
pnpm next build --turbopack
# 如果你想临时回退到 Webpack(迁移过渡期可用)
pnpm next build --webpack
注意 --webpack 标志在 Next.js 16 中仍可用,但在 17 中将移除。我建议把它当成过渡期的逃生通道,而不是长期方案。Next.js 官方 Turbopack 文档会持续同步当前可用的旗标。
从 webpack(config) 迁移到 turbopack 字段
这一步是迁移里最容易踩坑的部分。Turbopack 不支持 Webpack 的 plugin API(这是它能跑得快的根本原因),但提供了三个声明式 API 来覆盖 95% 的实际需求:rules、resolveAlias、resolveExtensions。
常见迁移对照
| 需求 | Webpack 写法 | Turbopack 写法 |
| SVG 作为 React 组件 | @svgr/webpack loader | turbopack.rules['*.svg'] + @svgr/webpack loader(兼容) |
| 路径别名 | config.resolve.alias | turbopack.resolveAlias |
| 自定义后缀解析 | config.resolve.extensions | turbopack.resolveExtensions |
| Bundle analyzer | @next/bundle-analyzer | 原生支持(已适配 Turbopack) |
| Module Federation | @module-federation/nextjs-mf | 暂不支持,等待 RFC |
| 自定义 plugin | 任意 plugin | 无对应 API,需重新设计 |
实际 next.config.ts 示例
import type { NextConfig } from "next";
const config: NextConfig = {
turbopack: {
rules: {
// 把 .svg 经过 SVGR loader 变成 React 组件
"*.svg": {
loaders: ["@svgr/webpack"],
as: "*.tsx",
},
// 把 GraphQL schema 文件当作字符串导入
"*.graphql": {
loaders: ["raw-loader"],
as: "*.js",
},
},
resolveAlias: {
"@ui": "./packages/ui/src",
"@design-tokens": "./packages/design-tokens/src/index.ts",
},
resolveExtensions: [
".mdx",
".tsx",
".ts",
".jsx",
".js",
".mjs",
".json",
],
},
};
export default config;
一条经验:把 webpack() 整个删掉,然后只迁移真正被消费的规则。很多团队的旧配置里 70% 是早年留下、现在没人记得为什么存在的死代码。从 fetch 到 use cache 的数据获取指南里也提过这种"考古式清理"思路,构建配置同样适用。
持久化缓存与远端缓存怎么配
Turbopack 在内存里有一份 Turbo Engine 图,每次 next build 结束后这份图就丢了,所以默认情况下 CI 里每次构建都是冷启动。Next.js 16 引入 experimental.turbopackPersistentCaching,把图序列化到磁盘,下一次构建可以在 200ms 内恢复整张图,再增量执行变化的 task。
// next.config.ts
const config: NextConfig = {
experimental: {
turbopackPersistentCaching: true,
},
};
缓存默认写到 .next/cache/turbopack/。在 CI 里要让它生效,需要把这条路径加入缓存 key。GitHub Actions 的写法:
- name: Restore Turbopack cache
uses: actions/cache@v4
with:
path: |
.next/cache/turbopack
.next/cache/webpack
key: ${{ runner.os }}-turbopack-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbopack-${{ hashFiles('**/pnpm-lock.yaml') }}-
键里包含 lockfile hash 是关键。一旦依赖变化,旧缓存几乎不可复用,强行复用会导致解析失败(我第一次踩这个坑花了两小时定位)。如果你在用 Vercel,Vercel Build Output v3 已经自动处理这层缓存。Turborepo 用户可以走远端缓存把缓存共享给团队成员,本地构建也能命中。
SWC、Lightning CSS 与 Turbopack 的协作关系
很多人以为 Turbopack 就是 SWC + Rust 重写。这是误解。Turbopack 是调度层,它编排谁来处理哪个文件。SWC 是 JS/TS 的转译后端,Lightning CSS 是 CSS 的解析与压缩后端,React Server Components 编译器是另一段 Rust 代码,专门把 "use client" / "use server" 边界编译成正确的 client reference 表。
这种分层让你能在不动 Turbopack 的前提下单独升级某一层。例如 Lightning CSS 在 1.30 引入了原生 nesting 支持,升级它就能让 :has() 与 CSS nesting 的 polyfill 体积下降。你不需要改 next.config.ts,跑 pnpm update lightningcss 即可。
与 React Compiler 的关系
React Compiler(曾经叫 React Forget)在 Next.js 16 中可以通过 experimental.reactCompiler: true 打开。它会在 Turbopack 流水线的 SWC 阶段之后插入一道 IR pass,自动给组件加 memoization。生产构建里这一步的成本约占总构建时间的 5–8%,但运行时性能收益通常大于这点构建开销。React 官方文档对启用条件有完整说明。
生产构建性能基准与监控
给一些具体数字,方便你评估升级的预期收益。下面是我们在三个项目上测得的对比(同台 M2 Pro,16 核 vCPU CI runner)。
| 项目规模 | Webpack 冷构建 | Turbopack 冷构建 | Turbopack 热构建(CI 缓存命中) |
| 小型营销站 (45 路由) | 52s | 14s | 4s |
| 中型 SaaS 仪表盘 (320 路由) | 3m48s | 41s | 9s |
| Monorepo 多 app (1200 路由) | 9m12s | 1m48s | 22s |
怎么持续追踪
开启 NEXT_TURBOPACK_TRACING=1 next build 会在 .next/trace.log 产出 Chrome trace event 格式的火焰图,丢进 chrome://tracing 或 Perfetto 就能看到每个 task 的耗时分布。我们在 CI 里跑一段简单的 jq 脚本把 95 分位的 task 抽出来,长期跑回归。任何一次 PR 让某条 task 从 200ms 涨到 2s,CI 就报警。这是 build-system 团队最该投资的可观测性。
Turborepo / pnpm workspaces 中的注意事项
Turbopack 与 Turborepo 都来自 Vercel,但它们是两个独立的工具。Turborepo 是任务编排器,Turbopack 是单个 Next.js app 的打包器。两者一起用时有几个工程细节值得提前知道。
- workspace 包不再需要 transpile:Turbopack 默认会按 source 编译 workspace 范围内的所有包,
next-transpile-modules 与 transpilePackages 配置对大多数项目可以删除。例外是发布到 npm 的 dual-package(含 main 与 module 字段),这种保留 transpilePackages 更稳。
- 共享缓存:Turborepo 的远端缓存只缓存 task 输出,不缓存 Turbopack 内部图。两层缓存协作时,确保 Turborepo task 的
outputs 包含 .next/cache/turbopack/**。
- 循环依赖检测:Turbopack 在生产构建里对 workspace 间循环依赖更严格,dev 模式能跑通的循环可能在 build 报错。提前用
madge --circular 检查。
如果你的项目还在用 Drizzle ORM 与 Next.js 全栈结构,注意 Drizzle 的 schema 文件常被多个 app 共享。迁移时把这些共享模块的 alias 显式写到 turbopack.resolveAlias,可以避免 Turbopack 走错解析路径。
为什么我的 Turbopack 构建失败了?排查清单
大部分迁移失败可以归到下面五类。按顺序排查,平均 30 分钟内能定位。
1. "Module not found" 但 Webpack 能找到
八成是 resolve.extensions 或 resolveAlias 没迁移完整。检查你引用的文件后缀是否在 turbopack.resolveExtensions 列表里。常被遗漏的是 .cts、.mts 与自定义 .gql。
2. CSS @import 路径解析失败
Lightning CSS 比 PostCSS 更严格,相对路径必须有 ./ 前缀。把不规范的 @import "foo.css" 显式改成 @import "./foo.css",可以用 codemod 批处理,但跑前先 commit 一次。
3. "use client" 边界报错
RSC 编译器要求 "use client" 必须是文件第一条非注释语句。Webpack 历史上容忍它放在 import 之后,Turbopack 不容忍。修复就是把它挪到文件头。如果你之前花了功夫做中间件与 Edge Runtime 的边界设计,那些边界声明也要顺手检查。
4. 构建挂在 "Compiling..." 不动
通常是磁盘 IO 被 ESLint 或 type check 抢光。Next.js 16 默认把 type check 并行到独立进程,但如果你在 CI 上限制了 worker 数,Turbopack 会等 IO。把 NEXT_TURBOPACK_TRACING=1 打开看 trace 里哪条 task 卡住。
5. Bundle 体积比 Webpack 大
Turbopack 的 tree shaking 在 1.0 时不如 Webpack 在某些边角场景。Next.js 16.1 之后这个差距在大多数项目里已经收敛到 ±3%,但如果你看到 10% 以上的差距,先检查是不是 sideEffects: false 没在依赖的 package.json 里声明。
常见问题
Turbopack 比 Webpack 快多少?
在我们测的三个项目里,冷启构建 Turbopack 比 Webpack 快 3.7×–5.1×。启用持久化缓存后,CI 二次构建可以快 10×–24×。具体倍数取决于项目大小、TS 文件数与 CSS 体积,越大的项目收益越显著。
Turbopack 生产构建稳定吗?可以上生产了吗?
可以。Next.js 16 把 Turbopack 设为默认引擎,意味着 Vercel 已经在所有官方部署里用它跑生产构建。已知限制集中在 Module Federation 与少量长尾 Webpack plugin 生态,不涉及主流应用栈。
我必须迁移所有自定义 Webpack 配置吗?
不必。Next.js 16 仍支持 --webpack 旗标作为过渡逃生通道,但它在 Next.js 17 中会被移除。建议把迁移当成两步走:先用 --webpack 跑通 16 升级,再在一个独立 PR 里完成 webpack() 到 turbopack 的迁移。
Turbopack 支持 Module Federation 吗?
截至 2026 年 6 月,原生不支持。Vercel 与 Module Federation 团队联合发布了 RFC,预计在 Next.js 16.4 进入实验阶段。如果你的微前端依赖 MF,暂时用 --webpack,或评估改用 Next.js 内置的 multi-zones 拼接。
如何在 CI 中持久化 Turbopack 缓存?
开启 experimental.turbopackPersistentCaching,然后在 CI 缓存配置里包含 .next/cache/turbopack,缓存 key 加入 lockfile hash。GitHub Actions、CircleCI、GitLab CI 的 cache action 都能直接用,命中后二次构建通常落在 10–30 秒区间。