Turbopack 的 instrumentation 坑:守卫条件写错,dev 侧效默默不跑
把 Next.js 开发服务器从 webpack 切到 Turbopack(next dev --turbopack),启动快了不少。但随手打开 terminal 一看,每次请求都刷出一屏警告:
⚠ ./src/instrumentation.ts:27:3
Ecmascript file had an error
> 27 | process.on('unhandledRejection', (reason) => {
A Node.js API is used (process.on at line: 27) which is not supported in the Edge Runtime.
process.on、process.pid、process.version 挨个被点名,警告刷得停不下来——尽管页面一切正常,路由全部 200。
现象
Turbopack 接管之前,同一份 instrumentation.ts 从来没有任何警告;切过来之后,每次 hot reload 都重新刷一遍。暂时在终端里看习惯了,后来按官方文档把 Node 专属代码拆到 instrumentation-node.mjs,并加了守卫:
// instrumentation.ts — 官方文档推荐写法
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation-node.mjs')
}
}警告果然消失了。但过了两天发现:dev 环境下,启动日志从来没有出现过,process.on('unhandledRejection', ...) 也没有被注册上——本该在 Node 侧执行的初始化代码,一行都没跑。
根因
Turbopack 将 instrumentation.ts 同时编译进 Node.js entry 和 Edge entry(中间件、Edge route 都依赖 edge 那份)。它对 edge entry 做静态扫描,发现 process.on 就报警——这是 Turbopack 的主动检测,webpack 没有这一步所以从来不吭声。
解法方向是对的:把 Node 专属代码移到独立文件,用动态 import() 加守卫,让 Turbopack 发现"edge 路径不可能走到这个 import",从而把 node 模块排除出 edge bundle,警告自然消失。
问题出在守卫的条件上:
next dev 启动时,NEXT_RUNTIME 是空字符串 '',不是 'nodejs'。生产构建(next build && next start)才会把它设成 'nodejs'。
所以 === 'nodejs' 这个守卫在 dev 下永远走 false 分支,动态 import 被整个跳过——侧效从来不执行,但因为没有任何报错,你只能靠"启动日志有没有出现"才能察觉。
=== 'nodejs' 版本的诡异之处在于:0 个警告 + 0 条启动日志——看起来干净,实际上坏了。
正解
把守卫条件从 === 'nodejs' 改成 !== 'edge':
// src/instrumentation.ts
export async function register() {
// !=='edge',而非 ==='nodejs'
// dev 时 NEXT_RUNTIME==='',prod 时 ==='nodejs',两种情况都能通过
// edge 被排除,Turbopack 不会把 node 模块打进 edge bundle → 警告消失
if (process.env.NEXT_RUNTIME !== 'edge') {
await import('./instrumentation-node.mjs')
}
}// src/instrumentation-node.mjs — node 专属侧效放在这里
process.on('unhandledRejection', (reason) => { /* → 日志 sink */ })
process.on('uncaughtException', (err) => { /* → 日志 sink */ })
// 启动日志、请求计时初始化等……Turbopack 对 !== 'edge' 的静态分析结论与 === 'nodejs' 一致——都能推断 edge 路径不可达,从而把 node 模块排除出 edge bundle,警告归零。唯一的区别在 dev 时:'' 过不了 === 'nodejs',但能过 !== 'edge',侧效正常执行。
!== 'edge' 一个条件解决两件事:Turbopack 把 node 模块排除出 edge bundle(警告归零)+ dev/prod 两种运行时都能进入 node 侧效分支(侧效正常)。
附:eslint flat-config 的连带报错
把 instrumentation-node.mjs 放到 src/ 根目录时,eslint 可能报:
src/instrumentation-node.mjs
15:1 error 'process' is not defined no-undef
原因是 node-globals 的 files glob 只覆盖了 src/server/**/*.mjs、src/lib/**/*.mjs 等子目录,没有包含 src/ 根级 .mjs 文件。在 eslint 配置里把 'src/*.mjs' 加进去即可,不要把文件挪位置来绕过 lint。
验证:靠侧效结果,不靠警告数量
"警告消失了"不等于"侧效跑了"。验证方式:
- 查启动日志 sink,确认有
nextjs.boot条目 - 或在
register()最顶部临时加console.log(process.env.NEXT_RUNTIME),dev 下应输出空字符串''
=== 'nodejs' 守卫产生"0 警告 + 0 侧效"的假阳性干净状态,只有主动检查侧效输出才能发现问题。养成"改了初始化代码就去确认启动日志"的习惯。
一句话外卖
next dev时NEXT_RUNTIME是空字符串,instrumentation 的 node 守卫必须用!== 'edge'而非=== 'nodejs',否则 dev 侧效静默跳过。