Next.js 错误处理完全指南(Pages 与 App Router)
Next.js 的混合渲染模型带来独特的错误处理挑战。本文覆盖 Pages Router 与 App Router 两套体系的错误边界、服务端组件异常、API 路由错误与生产监控方案。
App Router 用文件约定分层兜底:error.tsx 捕获路由段错误、global-error.tsx 兜住根布局错误、not-found.tsx 处理 404;服务端错误的堆栈在生产环境自动脱敏。Pages Router 则用 _error.tsx 与 Error Boundary 组件。
Next.js 错误的特殊性
混合渲染让错误可能发生在四个位置:服务端渲染(SSR/RSC)、客户端组件、API 路由/Server Action、构建期。每层的捕获机制不同——这正是 Next.js 错误处理看起来"复杂"的原因:不是规则多,是层面多。
App Router:文件即错误边界
app/
├── layout.tsx
├── page.tsx
├── error.tsx ← 本路由段的错误边界
├── global-error.tsx ← 根错误边界(捕获 layout 的错误)
├── not-found.tsx ← notFound() 触发的 404 页
└── dashboard/
└── error.tsx ← 嵌套路由的局部边界
// app/dashboard/error.tsx
'use client'; // 错误边界必须是客户端组件
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>出了点问题</h2>
<button onClick={reset}>重试</button>
</div>
);
}
要点:错误边界按路由段就近捕获——dashboard 出错不影响侧边栏导航;reset() 尝试重新渲染该段;生产环境下 RSC 错误只透传 digest 编号而非消息,敏感信息天然脱敏。
数据获取与 Server Action 的错误
// 服务端组件:手动处理预期内错误
async function Page({ params }) {
const user = await getUser(params.id).catch(() => null);
if (!user) notFound(); // 渲染 not-found.tsx
return <Profile user={user} />;
}
// Server Action:返回错误对象而非抛出
async function updateProfile(formData: FormData) {
'use server';
const parsed = schema.safeParse(formData);
if (!parsed.success) {
return { error: 'VALIDATION_FAILED', fields: parsed.error.flatten() };
}
// ...
}
Server Action 的错误对象要可序列化;预期内错误返回结构化结果,预期外错误抛出交给最近的 error.tsx。
Pages Router 体系
老路由体系下:自定义 pages/_error.tsx 渲染错误页(SSR 与客户端通用);getServerSideProps 里返回 notFound: true 触发 404;客户端组件树用 React Error Boundary 类组件包裹。API 路由的错误处理同 Express 思路:try/catch + 状态码 + 结构化 JSON。
串起浏览器报错与服务端异常
Next.js 的浏览器错误边界和服务端日志记录的是不同现场。浏览器端接入观测云 RUM,并上传与发布版本匹配的 SourceMap;Node.js 服务端另接入 APM 和日志采集。生产错误中的 digest 可作为补充检索线索,不能替代分布式 trace_id。跨端串联还需要追踪头传递与后端插桩配合,避免把服务端错误堆栈直接暴露给浏览器。
接入文档:Web RUM 接入 · SourceMap 配置 · Node.js APM 接入 · 日志与链路关联。
常见问题(FAQ)
Q:error.tsx 为什么必须是客户端组件?
错误边界基于 React 的 componentDidCatch/getDerivedStateFromError 机制,只有客户端组件支持。'use client' 声明即可,边界内依然可以渲染服务端组件作为子元素。
Q:global-error.tsx 和 error.tsx 的分工?
error.tsx 管不了同级的 layout.tsx(边界在布局之内);根布局自身出错只有 global-error.tsx 能接住——它必须包含完整的 html/body 标签。生产环境两个都要有。
Q:开发环境能看到详细错误,生产只有 digest 怎么办?
这是 RSC 的安全特性(防止堆栈泄露)。用 digest 关联服务端日志:在 error.tsx 中把 digest 上报到日志/监控平台,与服务端错误日志按 digest 匹配还原现场。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。