Expected vs uncaught exceptions
Errors are divided into two categories: expected errors that occur during normal operation (such as form validation or failed requests) and should be handled explicitly and returned to the client, and uncaught exceptions that are unexpected bugs which should be caught by error boundaries.
useActionState hook for server function error handling
Use the useActionState hook from React to handle expected errors in Server Functions. Model expected errors as return values rather than using try/catch blocks. The hook returns [state, formAction, pending] where state contains the error information returned from the server function.
Server component error handling with conditional rendering
In Server Components, check the response status after fetching data and conditionally render an error message or use the redirect function if the request failed.
notFound function for 404 errors
Call the notFound function from 'next/navigation' within a route segment when a resource is not found. Pair it with a not-found.js file in the same segment to display a 404 UI.
error.js file creates error boundary
Create an error boundary by adding an error.js file inside a route segment and exporting a React component. Error boundaries must be Client Components (marked with 'use client'). The exported component receives error and retry props, where error is an Error object that may contain a digest property, and retry is a function to attempt recovery.
Error boundary component structure
An error boundary component receives error (Error & { digest?: string }) and retry (() => void) as props. It should use useEffect to log errors and can provide a retry button that calls the retry function to re-fetch and re-render the segment.
Error boundaries bubble up to nearest parent
Errors 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.
catchError function for component-level error recovery
Use the catchError function from 'next/error' to create error boundaries that wrap any part of your component tree. It takes a fallback component that receives props and an ErrorInfo object containing error and retry properties. Returns a wrapped component that can be used as a wrapper in layouts or pages.
Error boundaries don't catch event handler errors
Error boundaries are designed to catch errors during rendering, not in event handlers or async code. Errors in event handlers run after rendering and won't be caught by boundaries. Manually catch these errors and store them using useState or useReducer to update the UI.
Errors in useTransition bubble to error boundary
Unhandled errors inside startTransition from useTransition will bubble up to the nearest error boundary, unlike regular event handler errors which don't trigger boundaries.
global-error.js for root layout errors
Handle errors in the root layout using the global-error.js file located in the root app directory. Global error UI must be a Client Component and must define its own <html> and <body> tags, since it replaces the root layout or template when active.
Example: useActionState with server function error handling
Server function example showing expected error handling:
```ts
'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' }
}
}
```
Client component using useActionState to display errors:
```tsx
'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>
)
}
```
Example: notFound function usage
Using notFound in a page component:
```tsx
import { notFound } from 'next/navigation'
import { getPostBySlug } from '@/lib/posts'
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = getPostBySlug(slug)
if (!post) {
notFound()
}
return <div>{post.title}</div>
}
```
Corresponding not-found component:
```tsx
export default function NotFound() {
return <div>404 - Page Not Found</div>
}
```
Example: error.js error boundary
Error boundary implementation:
```tsx
'use client' // Error boundaries must be Client Components
import { useEffect } from 'react'
export default function ErrorPage({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={() => retry()}
>
Try again
</button>
</div>
)
}
```
Example: catchError component-level error boundary
Creating a component-level error boundary with catchError:
```tsx
'use client'
import { catchError, type ErrorInfo } from 'next/error'
function ErrorFallback(props: { title: string }, { error, retry }: ErrorInfo) {
return (
<div>
<h2>{props.title}</h2>
<p>{error.message}</p>
<button onClick={() => retry()}>Try again</button>
</div>
)
}
export default catchError(ErrorFallback)
```
Using the wrapped component:
```tsx
import ErrorBoundary from './custom-error-boundary'
export default function Component({ children }: { children: React.ReactNode }) {
return <ErrorBoundary title="Dashboard Error">{children}</ErrorBoundary>
}
```
Example: manual event handler error handling
Manually handling errors in event handlers using useState:
```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>
)
}
```
Example: global-error.js at root level
Global error handler for root layout:
```tsx
'use client' // Error boundaries must be Client Components
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => retry()}>Try again</button>
</body>
</html>
)
}
```