Next.js Error Boundaries
Next.js App Router 的 error.tsx 为 route segment 建立客户端错误边界,用于隔离未处理异常、保留外层 Layout,并通过 reset() 尝试恢复失败子树。
[!info] related notes
- 所属 MOC:
- 前置概念: declarative-error-boundary、nextjs-layouts
- 并列概念: nextjs-not-found-handling、nextjs-loading-and-streaming
- 易混淆概念:
- 关系笔记:
Next.js Error Boundaries
一句话定义
error.tsx 是 Next.js App Router 为某个 route segment 建立的错误边界 fallback;当其包裹的子路由树出现未处理异常时,框架可以用错误 UI 替换失败子树,同时尽量保留仍然健康的父级 Layout。
基本模型
目录:
app/
└── (app)/
├── layout.tsx
└── goals/
├── error.tsx
└── [goalId]/page.tsx
如果 [goalId]/page.tsx 中出现未处理异常:
AppLayout ← 健康,继续保留
└── Goals Boundary
└── GoalPage ← 抛出异常
↓
error.tsx ← 替换失败子树
这体现的是 UI 层的 fault isolation。
为什么 error.tsx 是 Client Component
典型写法:
"use client"
export default function Error({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div>
<h2>加载失败</h2>
<button onClick={() => reset()}>
重试
</button>
</div>
)
}
这里需要:
onClick
reset()
客户端交互状态
所以 fallback 本身需要客户端运行时。
reset() 的语义
reset() 不是“刷新整个浏览器页面”,而是尝试重新渲染当前错误边界包裹的 route segment。
适合处理:
- 短暂网络失败
- 临时后端异常
- 偶发数据请求失败
不适合掩盖确定性的代码 Bug。如果同一逻辑必然抛错,反复 reset 也不会真正解决问题。
Error Boundary 的层级
错误边界应该尽量靠近真正可能失败、且值得独立恢复的区域。
App Error Boundary
└── Goals Error Boundary
└── Goal Detail
越近的边界通常越适合提供更具体的恢复 UI;更高层边界负责更大范围的兜底。
这和后端 fault containment 的原则相似:
局部故障优先局部隔离,不要因为一个子页面失败就让整个应用壳一起消失。
一个重要边界:同 segment 的 Layout 错误
不要误以为:
segment/error.tsx
一定能捕获同目录所有文件的异常。
error.tsx 主要包裹该 segment 的子内容;如果错误发生在同 segment 的 layout.tsx 本身,需要由更高一层的错误边界处理。
所以:
segment/layout.tsx 出错
↓
需要 parent error boundary
这也是为什么错误边界应该按路由树分层设计,而不是只放一个“万能 error.tsx”。
与通用 React Error Boundary 的关系
Next.js 没有发明“错误边界”这个思想,它是在 React 错误边界机制基础上增加 App Router 文件约定与 route segment 作用域。
通用 React 机制见 声明式错误边界 Error Boundary。
可以这样理解:
React Error Boundary
↓ framework convention
Next.js error.tsx
↓
route-segment fault isolation
Error 与 Not Found 必须区分
数据库连接失败:
unexpected failure
→ error.tsx
数据库正常查询,但资源不存在:
expected absence
→ notFound()
→ not-found.tsx
不要用:
throw new Error("Goal not found")
把正常 404 伪装成系统异常。见 Next.js Not Found 处理。
与 Layout 状态保留的关系
如果错误发生在 Layout 的子路由中,而 Layout 本身仍健康:
AppLayout
├── Sidebar(collapsed=true) ← 保留
└── Goals
└── Error UI
外层 Layout 不需要因为子路由失败而自动重建,因此其中被保留的客户端状态通常也能继续存在。
常见误区
error.tsx 不是业务校验失败的统一入口
诸如“表单邮箱格式错误”“Goal 不存在”不一定属于异常边界。Expected domain outcome 应使用更准确的业务状态表达。
有 error boundary 不等于不用监控
error.tsx 负责用户侧降级 UI,不代表异常就不需要日志、观测和告警。
reset 不是修复 Bug
它只是恢复机制,不是错误处理架构的替代品。
最短记忆方式
error.tsx
= route-level fault boundary
+ fallback UI
+ reset/recovery entry
它主要回答:这一层出现未处理异常时,如何局部失败而不是整棵应用树一起失败?