Next.js Error Handling
Handle errors in Next.js with error boundaries, custom 404 pages, and safe form error messages.
TL;DR
- Add
error.tsxto catch errors thrown in a route segment. - Call
notFound()to render the nearestnot-found.tsxpage. - Return expected errors from a Server Action and read them with
useActionState.
Error Boundaries
error.tsxShow a fallback when the segment throws; retry() refetches it in Next.js 16.3 and later.
'use client';
export default function Error({
error,
retry,
}: {
error: Error & { digest?: string };
retry: () => void;
}) {
return <button onClick={retry}>Try again</button>;
}error.digestLog the digest to match a hidden production error with your server logs.
useEffect(() => {
console.error(error.digest, error);
}, [error]);global-error.tsxCatch errors in the root layout; it must render its own html and body tags.
'use client';
export default function GlobalError() {
return (
<html>
<body>
<h1>Something went wrong</h1>
</body>
</html>
);
}Not Found Pages
not-found.tsxRender a custom 404 page for unknown URLs across the whole app.
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return <Link href="/">Back to home</Link>;
}notFound()Trigger the 404 page from a page when the requested data does not exist.
import { notFound } from 'next/navigation';
const post = await getPost(slug);
if (!post) notFound();Segment Not FoundAdd a not-found.tsx inside a folder to customize the message for that section.
app/
not-found.tsx // site-wide
blog/
not-found.tsx // blog only
[slug]/page.tsxExpected Errors
Return an ErrorReturn a message from a Server Action instead of throwing for bad input.
'use server';
export async function signUp(
_prev: { error: string },
form: FormData
) {
const email = String(form.get('email') ?? '');
if (!email.includes('@')) {
return { error: 'Enter a valid email' };
}
await createUser(email);
return { error: '' };
}useActionState()Read the returned error in a Client Component and show it beside the form.
'use client';
import { useActionState } from 'react';
import { signUp } from './actions';
export function Form() {
const [state, action] = useActionState(signUp, {
error: '',
});
return (
<form action={action}>
<input name="email" />
<p>{state.error}</p>
</form>
);
}res.okCheck the response status and show a message instead of crashing the page.
const res = await fetch(url);
if (!res.ok) {
return <p>Could not load prices.</p>;
}Unexpected Errors
throwThrow from a Server Component to hand control to the nearest error boundary.
const post = await getPost(id);
if (!post.published) {
throw new Error('Post is not published');
}try / catchWrap a risky call and return a safe fallback value when it fails.
async function getRate() {
try {
const res = await fetch(url);
return (await res.json()).rate;
} catch {
return null;
}
}onRequestError()Report every server error from one place by exporting this from instrumentation.ts.
// instrumentation.ts
export async function onRequestError(
err: unknown,
request: { path: string }
) {
await reportError(err, request.path);
}Tips
- Log the
error.digestvalue fromerror.tsx, because it matches the full error in your server logs. - Place
error.tsxfiles close to risky sections, so one failure replaces only that segment and the layout stays usable.
Warnings
- An
error.tsxfile cannot catch errors from thelayout.tsxin the same folder, so place it one level higher. - Error messages from Server Components are hidden in production, so never rely on
error.messageto show users details.
In Practice
Build an error boundary that logs the failure and lets the user try again.
- The file must be a Client Component, because error boundaries run in the browser.
- The effect logs the digest, which links this error to the server logs.
- The message stays generic, since production hides the real error text.
- retry refetches and re-renders the segment so the user can recover.
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({
error,
retry,
}: {
error: Error & { digest?: string };
retry: () => void;
}) {
useEffect(() => {
console.error('Dashboard error', error.digest);
}, [error]);
return (
<div role="alert">
<h2>We could not load your dashboard.</h2>
<button onClick={() => retry()}>Try again</button>
</div>
);
}FAQ
Add an error.tsx file to a route folder. It is a Client Component that renders when anything in that segment throws. Offer a button that calls retry(), or reset() on versions before 16.3.
Add app/not-found.tsx for a site-wide 404 page. Call notFound() from next/navigation in a page when the data does not exist.
error.tsx catches errors in one route segment and keeps the layouts above it. global-error.tsx catches errors in the root layout, so it must render its own and tags.
Return expected errors, like a failed validation, as a value and show them with useActionState. Throw only for unexpected failures, which the nearest error.tsx will catch.