先说结论:Next.js 16值得升,但升级过程的折腾程度远超我的预期。
事情是这样的。我有个跑了大半年的Next.js项目,之前一直用webpack做打包,偶尔开--turbopack跑dev,体验确实不错。看到16正式版的changelog写着"Turbopack稳定,推荐升级",我想着反正迟早要迁,不如趁现在没什么业务压力先搞定。 结果升级第一步就给了我一巴掌。
Turbopack成了默认,我的webpack配置不认了
跑完npx @next/codemod@canary upgrade latest,满心以为会像以前一样顺滑。然后CI直接红了。 报错很直白:检测到自定义webpack配置,拒绝build。 不是我配置写得有问题,是Turbopack现在根本不想搭理webpack了------它接管了打包流程,碰到自定义webpack配置直接报错,生怕你稀里糊涂跑出不兼容的产物。而我那个next.config.js里恰好有一段接Sentry时加的webpack fallback配置,跑了一直没动过,这次直接成了绊脚石。 官方的建议是先加--turbopack标志强制走新流程,或者加--webpack暂时回退。我选了前者,把CI先救活再说。build总算过了,心想最难的应该已经搞定了。结果dev一跑,页面白屏。
params变成了Promise,我整个页面都挂了
控制台报的是params.slug is not iterable。我的博客详情页写法在Next.js 15里跑了大半年没出过任何问题:
javascript
export default async function Page({ params }: { params: { slug: string } }) {
const { slug } = params
// ...
}
到了16,params变成了Promise。不是建议你用await,是你不用await就报错。searchParams、cookies()、headers()、draftMode()这些也全是Promise了------官方说"同步访问已完全移除",一点余地不留。
好在官方有codemod:
swift
npx @next/codemod@canary next-async-dynamic-apis .
跑了一遍,大部分页面自动改好了。改完长这样:
javascript
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
// ...
}
我松了口气,在本地多点了几个页面,都没问题。心想这升级也没网上说的那么恐怖嘛。 然后测试环境一部署,CSS又炸了。
Sass里的~前缀,Turbopack不认
报错是Can't resolve '~bootstrap/dist/css/bootstrap.min.css'。webpack时代用~前缀来引用node_modules里的样式,大家都这么写,Turbopack不认这个语法,只接受标准路径。
项目里引用了一堆第三方库的样式,挨个手动改太烦。Turbopack倒是给了个兼容方案:
css
const nextConfig: NextConfig = {
turbopack: {
resolveAlias: {
'~*': '*',
},
},
}
用resolveAlias把~映射掉。不过官方文档也写了,建议还是把代码里的~去掉,毕竟alias hack早晚要还。我想着既然都改了,干脆全部替换掉,也就几分钟的事。 改完,本地再跑,一切正常。我甚至开始跟同事说"升级搞定了,就那样吧"。 然后部署又挂了。
Node版本也得跟着升
CI机器跑的是Node 18,Next.js 16要求>= 20.9.0。报错就一行:
vbnet
Error: Next.js 16 requires Node.js >= 20.9.0
升级公告里确实写了这个要求,但我之前每次升Next.js都没在Node版本上栽过跟头,注意力全放在框架本身的breaking changes上了。结果这次不光Node要升,TypeScript也得5.1.0以上,我们有个老项目还停在5.0。 升个Next.js连带着把运行环境和编译器都升了一遍。这算是这次升级最容易被忽略的点------你盯着框架的迁移指南看,却忘了底下还有一整条工具链等着。
一些没用到的变化
Next.js 16还把middleware.ts改成了proxy.ts,导出名也从middleware变成handler。这个我们项目没用middleware,没踩到坑。但如果你的项目有自定义middleware,记得跑codemod的时候一起处理了,或者手动改。
折腾完了,说说值不值
整个升级过程花了大半天。最让人烦躁的不是哪个具体的bug,而是一种"以为搞定了结果又冒出来一个"的循环感------webpack改完、async改了、Sass改了,每个单拎出来都不复杂,但叠在一起就很消磨耐心。
不过话说回来,跑起来之后确实快得明显。dev服务器启动从之前起身倒杯水的功夫变成了几乎瞬间就绪,HMR热更新也感觉不到延迟了。这种体验上的提升是实实在在的。
新项目直接上Next.js 16没问题。老项目的话,升级之前先跑一遍codemod dry run看看会改哪些文件,重点关注这几样:webpack自定义配置、Async APIs(params/searchParams/cookies/headers)、Sass的tilde导入、Node和TypeScript版本、以及有没有middleware需要改proxy。
对了,这次升级最大的感受倒不是技术上的,而是对Next.js方向的判断更清楚了------Turbopack从可选变成默认,意味着Vercel认为它已经成熟到不需要留后路的程度。这种框架级别的决策很果断,作为使用者你能做的就是跟上。