Files
next.js/docs/01-app/01-getting-started/10-error-handling.mdx
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

373 lines
10 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Error Handling
description: Learn how to display expected errors and handle uncaught exceptions.
related:
title: API Reference
description: Learn more about the features mentioned in this page by reading the API Reference.
links:
- app/api-reference/functions/redirect
- app/api-reference/file-conventions/error
- app/api-reference/functions/not-found
- app/api-reference/file-conventions/not-found
---
Errors can be divided into two categories: [expected errors](#handling-expected-errors) and [uncaught exceptions](#handling-uncaught-exceptions). This page will walk you through how you can handle these errors in your Next.js application.
## 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](/docs/app/guides/forms) or failed requests. These errors should be handled explicitly and returned to the client.
### Server Functions
You can use the [`useActionState`](https://react.dev/reference/react/useActionState) hook to handle expected errors in [Server Functions](https://react.dev/reference/rsc/server-functions).
For these errors, avoid using `try`/`catch` blocks and throw errors. Instead, model expected errors as return values.
```ts filename="app/actions.ts" switcher
'use server'
export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
const res = await fetch('https://api.vercel.app/posts', {
method: 'POST',
body: { title, content },
})
const json = await res.json()
if (!res.ok) {
return { message: 'Failed to create post' }
}
}
```
```js filename="app/actions.js" switcher
'use server'
export async function createPost(prevState, formData) {
const title = formData.get('title')
const content = formData.get('content')
const res = await fetch('https://api.vercel.app/posts', {
method: 'POST',
body: { title, content },
})
const json = await res.json()
if (!res.ok) {
return { message: 'Failed to create post' }
}
}
```
You can pass your action to the `useActionState` hook and use the returned `state` to display an error message.
```tsx filename="app/ui/form.tsx" highlight={11,19} switcher
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
const initialState = {
message: '',
}
export function Form() {
const [state, formAction, pending] = useActionState(createPost, initialState)
return (
<form action={formAction}>
<label htmlFor="title">Title</label>
<input type="text" id="title" name="title" required />
<label htmlFor="content">Content</label>
<textarea id="content" name="content" required />
{state?.message && <p aria-live="polite">{state.message}</p>}
<button disabled={pending}>Create Post</button>
</form>
)
}
```
```jsx filename="app/ui/form.js" highlight={11,19} switcher
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
const initialState = {
message: '',
}
export function Form() {
const [state, formAction, pending] = useActionState(createPost, initialState)
return (
<form action={formAction}>
<label htmlFor="title">Title</label>
<input type="text" id="title" name="title" required />
<label htmlFor="content">Content</label>
<textarea id="content" name="content" required />
{state?.message && <p aria-live="polite">{state.message}</p>}
<button disabled={pending}>Create Post</button>
</form>
)
}
```
### Server Components
When fetching data inside of a Server Component, you can use the response to conditionally render an error message or [`redirect`](/docs/app/api-reference/functions/redirect).
```tsx filename="app/page.tsx" switcher
export default async function Page() {
const res = await fetch(`https://...`)
const data = await res.json()
if (!res.ok) {
return 'There was an error.'
}
return '...'
}
```
```jsx filename="app/page.js" switcher
export default async function Page() {
const res = await fetch(`https://...`)
const data = await res.json()
if (!res.ok) {
return 'There was an error.'
}
return '...'
}
```
### Not found
You can call the [`notFound`](/docs/app/api-reference/functions/not-found) function within a route segment and use the [`not-found.js`](/docs/app/api-reference/file-conventions/not-found) file to show a 404 UI.
```tsx filename="app/blog/[slug]/page.tsx" switcher
import { getPostBySlug } from '@/lib/posts'
export default async function Page({ params }: { params: { slug: string } }) {
const { slug } = await params
const post = getPostBySlug(slug)
if (!post) {
notFound()
}
return <div>{post.title}</div>
}
```
```jsx filename="app/blog/[slug]/page.js" switcher
import { getPostBySlug } from '@/lib/posts'
export default async function Page({ params }) {
const { slug } = await params
const post = getPostBySlug(slug)
if (!post) {
notFound()
}
return <div>{post.title}</div>
}
```
```tsx filename="app/blog/[slug]/not-found.tsx" switcher
export default function NotFound() {
return <div>404 - Page Not Found</div>
}
```
```jsx filename="app/blog/[slug]/not-found.js" switcher
export default function NotFound() {
return <div>404 - Page Not Found</div>
}
```
## Handling 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.
### Nested error boundaries
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.
Create an error boundary by adding an [`error.js`](/docs/app/api-reference/file-conventions/error) file inside a route segment and exporting a React component:
```tsx filename="app/dashboard/error.tsx" switcher
'use client' // Error boundaries must be Client Components
import { useEffect } from 'react'
export default function ErrorPage({
error,
unstable_retry,
}: {
error: Error & { digest?: string }
unstable_retry: () => 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 re-fetching and re-rendering the segment
() => unstable_retry()
}
>
Try again
</button>
</div>
)
}
```
```jsx filename="app/dashboard/error.js" switcher
'use client' // Error boundaries must be Client Components
import { useEffect } from 'react'
export default function ErrorPage({ error, unstable_retry }) {
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 re-fetching and re-rendering the segment
() => unstable_retry()
}
>
Try again
</button>
</div>
)
}
```
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](/docs/app/getting-started/project-structure#component-hierarchy).
<Image
alt="Nested Error Component Hierarchy"
srcLight="/docs/light/nested-error-component-hierarchy.png"
srcDark="/docs/dark/nested-error-component-hierarchy.png"
width="1600"
height="687"
/>
Error boundaries dont catch errors inside event handlers. Theyre designed to catch errors [during rendering](https://react.dev/reference/react/Component#static-getderivedstatefromerror) to show a **fallback UI** instead of crashing the whole app.
In general, errors in event handlers or async code arent handled by error boundaries because they run after rendering.
To handle these cases, catch the error manually and store it using `useState` or `useReducer`, then update the UI to inform the user.
```tsx
'use client'
import { useState } from 'react'
export function Button() {
const [error, setError] = useState(null)
const handleClick = () => {
try {
// do some work that might fail
throw new Error('Exception')
} catch (reason) {
setError(reason)
}
}
if (error) {
/* render fallback UI */
}
return (
<button type="button" onClick={handleClick}>
Click me
</button>
)
}
```
Note that unhandled errors inside `startTransition` from `useTransition`, will bubble up to the nearest error boundary.
```tsx
'use client'
import { useTransition } from 'react'
export function Button() {
const [pending, startTransition] = useTransition()
const handleClick = () =>
startTransition(() => {
throw new Error('Exception')
})
return (
<button type="button" onClick={handleClick}>
Click me
</button>
)
}
```
### Global errors
While less common, you can handle errors in the root layout using the [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error) file, located in the root app directory, even when leveraging [internationalization](/docs/app/guides/internationalization). Global error UI must define its own `<html>` and `<body>` tags, since it is replacing the root layout or template when active.
```tsx filename="app/global-error.tsx" switcher
'use client' // Error boundaries must be Client Components
export default function GlobalError({
error,
unstable_retry,
}: {
error: Error & { digest?: string }
unstable_retry: () => void
}) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => unstable_retry()}>Try again</button>
</body>
</html>
)
}
```
```jsx filename="app/global-error.js" switcher
'use client' // Error boundaries must be Client Components
export default function GlobalError({ error, unstable_retry }) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => unstable_retry()}>Try again</button>
</body>
</html>
)
}
```