Skip to main content

错误处理

错误可分为两类:预期错误和未捕获异常:

¥Errors can be divided into two categories: expected errors and uncaught exceptions:

  • 将预期错误建模为返回值:避免在服务器操作中使用 try/catch 来处理预期错误。使用 useFormState 管理这些错误并将它们返回给客户端。

    ¥Model expected errors as return values: Avoid using try/catch for expected errors in Server Actions. Use useFormState to manage these errors and return them to the client.

  • 使用错误边界来处理意外错误:使用 error.tsxglobal-error.tsx 文件实现错误边界,以处理意外错误并提供后备 UI。

    ¥Use error boundaries for unexpected errors: Implement error boundaries using error.tsx and global-error.tsx files to handle unexpected errors and provide a fallback UI.

处理预期错误

¥Handling Expected Errors

预期错误是应用正常运行期间可能发生的错误,例如来自 服务器端表单验证 或失败请求的错误。这些错误应该明确处理并返回给客户端。

¥Expected errors are those that can occur during the normal operation of the application, such as those from server-side form validation or failed requests. These errors should be handled explicitly and returned to the client.

处理来自服务器操作的预期错误

¥Handling Expected Errors from Server Actions

使用 useFormState 钩子来管理服务器操作的状态,包括处理错误。这种方法避免了预期错误的 try/catch 块,这些错误应该被建模为返回值而不是抛出的异常。

¥Use the useFormState hook to manage the state of Server Actions, including handling errors. This approach avoids try/catch blocks for expected errors, which should be modeled as return values rather than thrown exceptions.

'use server'

import { redirect } from 'next/navigation'

export async function createUser(prevState: any, formData: FormData) {
const res = await fetch('https://...')
const json = await res.json()

if (!res.ok) {
return { message: 'Please enter a valid email' }
}

redirect('/dashboard')
}
'use server'

import { redirect } from 'next/navigation'

export async function createUser(prevState, formData) {
const res = await fetch('https://...')
const json = await res.json()

if (!res.ok) {
return { message: 'Please enter a valid email' }
}

redirect('/dashboard')
}

然后,你可以将操作传递给 useFormState 钩子并使用返回的 state 显示错误消息。

¥Then, you can pass your action to the useFormState hook and use the returned state to display an error message.

'use client'

import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'

const initialState = {
message: '',
}

export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)

return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />

<p aria-live="polite">{state?.message}</p>
<button>Sign up</button>
</form>
)
}
'use client'

import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'

const initialState = {
message: '',
}

export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)

return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />

<p aria-live="polite">{state?.message}</p>
<button>Sign up</button>
</form>
)
}

很高兴知道:这些示例使用 React 的 useFormState 钩子,它与 Next.js App Router 打包在一起。如果你使用的是 React 19,请改用 useActionState。请参阅 React 文档 了解更多信息。

¥Good to know: These examples use React's useFormState hook, which is bundled with the Next.js App Router. If you are using React 19, use useActionState instead. See the React docs for more information.

你还可以使用返回的状态来显示来自客户端组件的 toast 消息。

¥You could also use the returned state to display a toast message from the client component.

处理来自服务器的预期错误组件

¥Handling Expected Errors from Server Components

在服务器组件内部获取数据时,你可以使用响应有条件地渲染错误消息或 redirect

¥When fetching data inside of a Server Component, you can use the response to conditionally render an error message or redirect.

export default async function Page() {
const res = await fetch(`https://...`)
const data = await res.json()

if (!res.ok) {
return 'There was an error.'
}

return '...'
}
export default async function Page() {
const res = await fetch(`https://...`)
const data = await res.json()

if (!res.ok) {
return 'There was an error.'
}

return '...'
}

未捕获的异常

¥Uncaught Exceptions

未捕获的异常是意外错误,表示在应用正常流程中不应发生的错误或问题。这些应该通过抛出错误来处理,然后由错误边界捕获。

¥Uncaught exceptions are unexpected errors that indicate bugs or issues that should not occur during the normal flow of your application. These should be handled by throwing errors, which will then be caught by error boundaries.

  • 常用:使用 error.js 处理根布局下方的未捕获错误。

    ¥Common: Handle uncaught errors below the root layout with error.js.

  • 可选的:使用嵌套的 error.js 文件(例如 app/dashboard/error.js)处理细粒度的未捕获错误

    ¥Optional: Handle granular uncaught errors with nested error.js files (e.g. app/dashboard/error.js)

  • 不常用:使用 global-error.js 处理根布局中未捕获的错误。

    ¥Uncommon: Handle uncaught errors in the root layout with global-error.js.

使用错误边界

¥Using Error Boundaries

Next.js 使用错误边界来处理未捕获的异常。错误边界捕获其子组件中的错误并显示后备 UI,而不是崩溃的组件树。

¥Next.js uses error boundaries to handle uncaught exceptions. Error boundaries catch errors in their child components and display a fallback UI instead of the component tree that crashed.

通过在路由段内添加 error.tsx 文件并导出 React 组件来创建错误边界:

¥Create an error boundary by adding an error.tsx file inside a route segment and exporting a React component:

'use client' // Error boundaries must be Client Components

import { useEffect } from 'react'

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])

return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
'use client' // Error boundaries must be Client Components

import { useEffect } from 'react'

export default function Error({ error, reset }) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])

return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}

如果你希望错误冒泡到父错误边界,你可以在渲染 error 组件时使用 throw

¥If you want errors to bubble up to the parent error boundary, you can throw when rendering the error component.

处理嵌套路由中的错误

¥Handling Errors in Nested Routes

错误将冒泡到最近的父错误边界。这允许通过将 error.tsx 文件放置在 路由层次结构 中的不同级别来进行细粒度的错误处理。

¥Errors will bubble up to the nearest parent error boundary. This allows for granular error handling by placing error.tsx files at different levels in the route hierarchy.

处理全局错误

¥Handling Global Errors

虽然不太常见,但你可以使用位于根应用目录中的 app/global-error.js 来处理根布局中的错误,即使利用 internationalization 也是如此。全局错误 UI 必须定义自己的 <html><body> 标签,因为它在活动时会替换根布局或模板。

¥While less common, you can handle errors in the root layout using app/global-error.js, located in the root app directory, even when leveraging internationalization. Global error UI must define its own <html> and <body> tags, since it is replacing the root layout or template when active.

'use client' // Error boundaries must be Client Components

export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
'use client' // Error boundaries must be Client Components

export default function GlobalError({ error, reset }) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}