首页 / Next.js 16 入门教程 / 错误处理

Next.js 16 入门教程

错误处理

本教程共 42 篇 · 第 13 篇 · 更新于 2026-07-30 · 约 7 分钟阅读

Next.jsNext.js 16 入门教程错误处理error.tsxnot-found错误边界

13. 错误处理

本节目标:学会区分预期错误和意外错误,掌握 error.tsx、not-found.tsx 和 global-error.tsx 的使用方法。

两类错误

Next.js 把错误分成两类:

  1. 预期错误:表单验证失败、请求 404、权限不足。这些是正常业务逻辑的一部分。
  2. 意外错误:代码 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 中的错误

useTransitionstartTransition 中抛出的错误会冒泡到最近的错误边界:

'use client'

import { useTransition } from 'react'

export function Button() {
  const [pending, startTransition] = useTransition()

  const handleClick = () =>
    startTransition(() => {
      throw new Error('出错了')
    })

  return <button onClick={handleClick}>点击</button>
}

流式渲染中的错误

如果错误发生在流式渲染开始之后:

  1. HTTP 状态码已经发送(200),无法更改
  2. 最近的 error.tsx 捕获错误
  3. 错误 UI 替换出错的部分,页面其他部分保持正常

小结

这一章我们学了错误处理的完整方案:

  1. 预期错误用返回值处理,不用 throw
  2. error.tsx 创建嵌套错误边界
  3. not-found.tsx 显示 404 页面
  4. global-error.tsx 兜底所有错误
  5. 错误边界只捕获渲染错误,不捕获事件错误
  6. unstable_retry 提供恢复机制

下一章,我们来学习加载态与流式渲染。