- 发布日期
第 37 讲:调试技巧大全:dev / Node inspector / source map / production debug
开发调试、Node inspector、source map 生产调试与性能分析工具箱
第 33 讲教你接住错误并上报,第 36 讲教你看 V8 profiler。本讲是调试器使用手册:dev 模式怎么单步、prod 容器里怎么抓 trace、source map 配置怎么影响 stack 显示、
__NEXT_SHOW_IGNORE_LISTED怎么用、RSC stack 怎么读、Chrome DevTools 与 VSCode 远程 attach 的实战配置。
学习目标
- 在 dev 模式 attach Chrome DevTools / VSCode 单步调 server-side code(包括 Server Component)。
- 解读 Next.js dev overlay 的 stack 与 source map 信息;用
__NEXT_SHOW_IGNORE_LISTED=true看完整 framework frame。 - 在 prod 容器里抓 heap snapshot / CPU profile,不重启服务。
- 看懂 RSC stack 的来源标记,区分 server stack / client stack / hydration stack。
- 用 5 类调试手段诊断生产事故:断点 / log / heap snapshot / CPU profile / strace。
一、Dev 模式 server-side 调试
1.1 用 --inspect 启 dev server
# 启动并暴露 inspect 端口(默认 9229)
NODE_OPTIONS='--inspect' pnpm next dev
# 或指定端口
NODE_OPTIONS='--inspect=0.0.0.0:9230' pnpm next dev
打开 Chrome → chrome://inspect → 看到 "next.js" 进程 → 点 "inspect"。
进入 Chrome DevTools 后:
- Sources 面板:能看到
webpack://(webpack)或turbopack://(turbopack)开头的虚拟 URL - 给 Server Component / Route Handler / Server Action 打断点
- 触发请求 → 命中断点 → 单步
1.2 --inspect-brk 启动时立即停在第一行
NODE_OPTIONS='--inspect-brk' pnpm next dev
适合调启动期初始化逻辑(instrumentation.ts 的 register)。
1.3 VSCode 配置 launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side (Node)",
"type": "node",
"request": "launch",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["dev"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "Next.js: debug client-side (Chrome)",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: attach to running",
"type": "node",
"request": "attach",
"port": 9229,
"skipFiles": ["<node_internals>/**"]
}
]
}
3 种配置:
- launch (Node):VSCode 自己启动
pnpm dev - launch (Chrome):调客户端
- attach:连到已经启动的
--inspect进程
1.4 多 worker 进程 inspect
Next.js dev 默认 fork 出 router-server + render-worker。单个 9229 端口只接到 1 个。如果断点没命中:
# 给所有子进程开 inspect
NODE_OPTIONS='--inspect=0.0.0.0:9229' pnpm next dev
# Node 自动给子进程递增端口:9230, 9231...
或在 chrome://inspect 里 "Open dedicated DevTools for Node" → 自动发现所有 inspect 进程。
二、Production 模式调试
dev 关掉了 minify、保留 stack、加了 source map。prod 完全不同。
2.1 prod 容器里临时打开 inspect
K8s pod 不能改启动参数?用 kill -USR1:
# 进容器
kubectl exec -it my-pod -- /bin/sh
# 给主进程 PID 1 发 USR1 信号
kill -USR1 1
# Node 会监听 127.0.0.1:9229,但需要端口转发到本地
# 在本地另开终端
kubectl port-forward my-pod 9229:9229
# 然后 chrome://inspect 连 localhost:9229
⚠️ 安全:inspect 端口暴露 = 任意代码执行。只 attach 到本地端口转发,不要 0.0.0.0 暴露。
2.2 抓 heap snapshot 不停服
// 在某个 admin-only route handler
import { writeHeapSnapshot } from "v8";
export async function POST(req: NextRequest) {
await requireAdmin(req);
const path = `/tmp/heap-${Date.now()}.heapsnapshot`;
writeHeapSnapshot(path);
return Response.json({ path });
}
调用后从容器拷出来:
kubectl cp my-pod:/tmp/heap-xxx.heapsnapshot ./
在 Chrome DevTools → Memory → Load → 选这个 snapshot。
对比 2 个时间点的 snapshot 找内存泄漏(Comparison view)。
2.3 抓 CPU profile
类似:
import { Session } from "node:inspector/promises";
export async function POST(req: NextRequest) {
await requireAdmin(req);
const session = new Session();
session.connect();
await session.post("Profiler.enable");
await session.post("Profiler.start");
// 等 N 秒(或让 admin 触发 stop)
await new Promise((r) => setTimeout(r, 30_000));
const { profile } = (await session.post("Profiler.stop")) as any;
const fs = await import("node:fs/promises");
const path = `/tmp/cpu-${Date.now()}.cpuprofile`;
await fs.writeFile(path, JSON.stringify(profile));
session.disconnect();
return Response.json({ path });
}
DevTools → Performance → Load 这个 .cpuprofile。
2.4 现成工具 0x / clinic
npx 0x -- node packages/next/dist/bin/next start
# 跑负载
# Ctrl+C
# 自动生成 火焰图 HTML
或 clinic(第 36 讲讲过)。
三、Source map 配置
3.1 next build 自动产 source map
prod build 默认:
- client bundle
_next/static/chunks/*.js有对应.js.map - server bundle 在
.next/server/里也有.map
但默认 productionBrowserSourceMaps: false → client map 不发到 CDN。打开:
// next.config.js
module.exports = {
productionBrowserSourceMaps: true,
};
⚠️ 公开 source map 意味着源码暴露。生产环境通常只上传到 Sentry / 自家服务,浏览器不公开访问。
3.2 Sentry 自动上传
pnpm add @sentry/nextjs
npx @sentry/wizard
它会在 next.config.js 里包一层 plugin,build 时把 source map 上传到 Sentry,client bundle 删掉本地 map。错误聚合时 Sentry 自动反查源码位置。
3.3 RSC 错误的 source map
Server Component 的 stack 在第 33 讲讲过:digest 化、隐藏内部细节。dev 模式有完整 stack,prod 没有。
如果你想在 prod 看完整 server stack(自查、不暴露给用户):
// instrumentation.ts → onRequestError
export const onRequestError = (err, req, ctx) => {
console.error("Full server stack:", err.stack); // 进 server log(不进 response)
Sentry.captureException(err, { contexts: { request: req, next: ctx } });
};
stack 不会发到客户端,但能进你的 log 系统。
四、__NEXT_SHOW_IGNORE_LISTED
const showIgnoreListed = process.env.__NEXT_SHOW_IGNORE_LISTED === 'true'
Next.js 默认会把 framework 内部 frame 收起:
Error: oops
at MyPage (app/foo/page.tsx:5:13)
at <ignore-listed frames> ← 折叠
at handleRequest (router-server.ts:120:5)
__NEXT_SHOW_IGNORE_LISTED=true 后:
Error: oops
at MyPage (app/foo/page.tsx:5:13)
at renderToStringStream (app-render.tsx:823:11)
at ServerRouteRenderer.handle (route-modules/...:45:9)
at handleRequest (router-server.ts:120:5)
...
适用场景:
- 调试自己改的 Next.js 源码
- 想看具体走哪个 route module
- bug report 给 Next.js 团队
同类 env
| Env | 效果 |
|---|---|
__NEXT_SHOW_IGNORE_LISTED=true | 不折叠 framework frame |
NEXT_PRIVATE_DEBUG_CACHE=true | IncrementalCache 打 debug log |
DEBUG=next:* | next.js 自带的 debug logger |
NEXT_TELEMETRY_DEBUG=1 | telemetry 不发送但 console |
NEXT_PRIVATE_PRINT_HANG_WARNING=true | 渲染挂起时打 stack |
五、Dev overlay 与 turbopack tracing
dev overlay = 浏览器右下角红色错误提示。它能展开看:
- 完整 stack(已 source-mapped)
- "Edit in editor" 跳到 VSCode 行
- 修复后自动消失(Fast Refresh)
如果 overlay 看不到错误(白屏),可能 root layout 崩了 → 应该走 global-error 但 dev 模式更可能就 console.error 完事。
turbopack 特有
turbopack 在 dev 模式有更细的 module tracing:
NEXT_TURBOPACK_TRACING=trace pnpm next dev
会在 .next/trace.log 写一份 turbopack 的所有 task 调用。配合 turbo-trace-viewer 看 task graph:
pnpm dlx turbo-trace-viewer .next/trace.log
排查 "为什么这个文件没有热更新"、"为什么这个 chunk 没拆开" 之类的问题。
六、Client 错误调试
6.1 错误来自 Client Component
dev:overlay 显示。prod:根据 error boundary 展示。
6.2 hydration mismatch
Error: Hydration failed because the initial UI does not match what was rendered on the server.
定位流程:
- 看 dev overlay 的 diff 提示:哪段 server HTML 与 client 不匹配
- 常见原因:
Date.now()/Math.random()在 render 期直接调用window.localStorage在 server 端为 undefined- 用了
useEffect但又同时在初始 render 改 DOM
- 修复:
- 时间戳类用
useEffect+useState推迟到 client - 或用
suppressHydrationWarning(仅作 last resort)
- 时间戳类用
6.3 客户端 source map
DevTools → Sources → 找 webpack:// 或 turbopack:// → 文件结构对应你的 src/。打断点不需要额外配置。
七、5 类调试手段对比
| 手段 | 场景 | 工具 |
|---|---|---|
| 断点 | 复现一次能命中的 bug | Chrome DevTools / VSCode |
| log | 偶发、需要在多个时刻看状态 | pino + Datadog |
| heap snapshot | 内存增长 / 泄漏 | DevTools Memory |
| CPU profile | CPU 100% / 慢 endpoint | DevTools Performance / 0x |
| strace / dtrace | I/O 异常、syscall 卡 | Linux strace、macOS dtrace |
strace 实战
# 哪个系统调用慢
strace -ttt -T -p $(pidof node) 2>&1 | head -100
# 文件 I/O 跟踪
strace -e trace=openat,read,write -p $(pidof node)
可以看到比如 read(7, ..., 65536) = 0 (timeout) 这种证据。
八、生产事故"3 看"模板
发生事故时:
- 看监控(第 34 讲):哪个 page 哪个 metric 异常?时间起点是什么?
- 看 trace(第 34 讲):哪个 span 慢/错?是上游、还是本地代码?
- 看 log(第 34 讲 + 第 33 讲 onRequestError):那一时刻具体的 error 是什么?
3 看完后,进入针对性调试:
- 是 hot path 慢?→ CPU profile
- 是内存爆?→ heap snapshot
- 是上游慢?→ 找上游团队
- 是逻辑 bug?→ dev 复现 + 断点
九、debugger; 语句
老办法但有效:
// app/foo/page.tsx
export default async function Foo() {
const data = await fetchData()
debugger; // 进 inspect 时停在这里
return <Stuff data={data} />
}
只对 --inspect 启动的进程有效,prod 没 inspect 时是 noop。
十、配套 fixture
fixtures/lecture-37/ 提供:
/leak:故意泄漏 closure → 多次访问 heap 增长/cpu:CPU hot loop → 抓 profile/hydration-badvs/hydration-good:hydration mismatch 演示app/api/debug/heap-snapshot/route.ts:触发 heap snapshot 写盘app/api/debug/cpu-profile/route.ts:触发 30s CPU profile
启动:
cd learning/nextjs-40-lectures/fixtures/lecture-37
pnpm install && pnpm build && NODE_OPTIONS='--inspect=0.0.0.0:9229' pnpm start
# 1. attach Chrome
# 2. curl http://localhost:3037/leak (×100)
# 3. curl -X POST http://localhost:3037/api/debug/heap-snapshot
# 4. /tmp/heap-*.heapsnapshot → Chrome Memory → Load
十一、本讲小结
- dev 用
--inspect+ Chrome / VSCode 单步;attach 现成进程也行。 - prod 容器用
kill -USR1临时开 inspect;本地端口转发不要暴露。 - heap snapshot / CPU profile 都可以不停服抓。
__NEXT_SHOW_IGNORE_LISTED=true看完整 framework stack。- source map 自动产但 prod 默认不公开;接 Sentry 自动上传。
- 事故"3 看":监控 → trace → log,再针对性调试。
下讲预告
第 38 讲《5 个真实生产案例复盘》。我会综合前面所有讲过的知识,剖析 5 个典型生产故障,从症状到 root cause 到修复 PR,演示完整事故复盘流程。