Next.js Error Handling

Handle errors in Next.js with error boundaries, custom 404 pages, and safe form error messages.

TL;DR

  1. Add error.tsx to catch errors thrown in a route segment.
  2. Call notFound() to render the nearest not-found.tsx page.
  3. Return expected errors from a Server Action and read them with useActionState.

Error Boundaries

    error.tsx

    Show 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.digest

    Log the digest to match a hidden production error with your server logs.

    useEffect(() => {
      console.error(error.digest, error);
    }, [error]);
    global-error.tsx

    Catch 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.tsx

    Render 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 Found

    Add 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.tsx

Expected Errors

    Return an Error

    Return 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.ok

    Check 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

    throw

    Throw 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 / catch

    Wrap 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

  1. Log the error.digest value from error.tsx, because it matches the full error in your server logs.
  2. Place error.tsx files close to risky sections, so one failure replaces only that segment and the layout stays usable.

Warnings

  1. An error.tsx file cannot catch errors from the layout.tsx in the same folder, so place it one level higher.
  2. Error messages from Server Components are hidden in production, so never rely on error.message to show users details.

In Practice

FAQ