错误处理
本教程共 42 篇 · 第 13 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
13. 错误处理
本节目标:学会区分预期错误和意外错误,掌握 error.tsx、not-found.tsx 和 global-error.tsx 的使用方法。
两类错误
Next.js 把错误分成两类:
- 预期错误:表单验证失败、请求 404、权限不足。这些是正常业务逻辑的一部分。
- 意外错误:代码 bug、数据库连接失败、未捕获的异常。这些不该发生。
处理方式不同:预期错误返回提示信息,意外错误用错误边界兜底。
处理预期错误
Server Actions 中的预期错误
不要用 try/catch 抛出预期错误,而是把它作为返回值:
'use server'
export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title') as string
if (!title || title.length < 3) {
return { message: '标题至少 3 个字符' }
}
await db.post.create({ data: { title } })
return { message: '创建成功' }
}
客户端用 useActionState 接收:
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
export function Form() {
const [state, formAction, pending] = useActionState(createPost, { message: '' })
return (
<form action={formAction}>
<input type="text" name="title" />
{state?.message && <p>{state.message}</p>}
<button disabled={pending}>创建</button>
</form>
)
}
Server Component 中的预期错误
export default async function Page() {
const res = await fetch('https://api.example.com/data')
if (!res.ok) {
return <div>请求失败,请稍后重试</div>
}
const data = await res.json()
return <div>{data}</div>
}
处理 404
用 notFound() 函数触发 404 页面:
import { notFound } from 'next/navigation'
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = await getPost(slug)
if (!post) {
notFound()
}
return <div>{post.title}</div>
}
然后在同目录创建 not-found.tsx:
export default function NotFound() {
return (
<div>
<h2>页面未找到</h2>
<p>你访问的内容不存在。</p>
</div>
)
}
处理意外错误
嵌套错误边界
创建 error.tsx 文件,自动成为错误边界:
// app/dashboard/error.tsx
'use client' // 错误边界必须是客户端组件
import { useEffect } from 'react'
export default function ErrorPage({
error,
unstable_retry,
}: {
error: Error & { digest?: string }
unstable_retry: () => void
}) {
useEffect(() => {
console.error(error)
}, [error])
return (
<div>
<h2>出错了</h2>
<p>{error.message}</p>
<button onClick={() => unstable_retry()}>
重试
</button>
</div>
)
}
Important
error.tsx必须是 Client Component。因为错误边界是 React 的客户端特性。
错误冒泡
错误会向上冒泡到最近的 error.tsx。你可以在不同层级放置 error.tsx 实现细粒度的错误处理:
app/
├── error.tsx # 捕获所有未处理的错误
├── dashboard/
│ ├── error.tsx # 只捕获 dashboard 下的错误
│ └── settings/
│ └── error.tsx # 只捕获 settings 下的错误
恢复机制
unstable_retry 函数尝试重新渲染出错的路由段:
<button onClick={() => unstable_retry()}>
重新加载
</button>
注意:这是实验性 API,未来可能变化。
全局错误边界
global-error.tsx 放在 app/ 目录根部,捕获所有未被处理的错误:
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
unstable_retry,
}: {
error: Error & { digest?: string }
unstable_retry: () => void
}) {
return (
<html>
<body>
<h2>系统错误</h2>
<p>发生了意外错误,请稍后重试。</p>
<button onClick={() => unstable_retry()}>重试</button>
</body>
</html>
)
}
Note
global-error.tsx必须包含<html>和<body>标签,因为它会替换整个根布局。
组件级错误边界
如果不想用文件约定的 error.tsx,可以用 unstable_catchError 创建自定义错误边界:
'use client'
import { unstable_catchError as catchError } from 'next/error'
function ErrorFallback(
props: { title: string },
{ error, unstable_retry }: any
) {
return (
<div>
<h2>{props.title}</h2>
<p>{error.message}</p>
<button onClick={() => unstable_retry()}>重试</button>
</div>
)
}
export default catchError(ErrorFallback)
使用:
import ErrorBoundary from './custom-error-boundary'
export default function Component({ children }) {
return (
<ErrorBoundary title="仪表盘错误">
{children}
</ErrorBoundary>
)
}
错误边界的限制
错误边界只捕获渲染期间的错误,不捕获:
- 事件处理函数中的错误
- 异步代码(setTimeout、Promise)中的错误
- 服务端代码(Server Components)的渲染错误
处理事件中的错误
'use client'
import { useState } from 'react'
export function Button() {
const [error, setError] = useState<Error | null>(null)
const handleClick = () => {
try {
throw new Error('出错了')
} catch (e) {
setError(e as Error)
}
}
if (error) {
return <div>错误:{error.message}</div>
}
return <button onClick={handleClick}>点击</button>
}
startTransition 中的错误
useTransition 的 startTransition 中抛出的错误会冒泡到最近的错误边界:
'use client'
import { useTransition } from 'react'
export function Button() {
const [pending, startTransition] = useTransition()
const handleClick = () =>
startTransition(() => {
throw new Error('出错了')
})
return <button onClick={handleClick}>点击</button>
}
流式渲染中的错误
如果错误发生在流式渲染开始之后:
- HTTP 状态码已经发送(200),无法更改
- 最近的
error.tsx捕获错误 - 错误 UI 替换出错的部分,页面其他部分保持正常
小结
这一章我们学了错误处理的完整方案:
- 预期错误用返回值处理,不用 throw
error.tsx创建嵌套错误边界not-found.tsx显示 404 页面global-error.tsx兜底所有错误- 错误边界只捕获渲染错误,不捕获事件错误
unstable_retry提供恢复机制
下一章,我们来学习加载态与流式渲染。