Errors and not-found pages

Next.js · Practical · 12 min

Robust apps fail gracefully per section instead of showing a blank page or a generic crash.

Next.js separates two kinds of errors.

Expected errors (validation failures, a 404 from an API) are part of normal operation. Model them as return values: render a message, or return an error object from a Server Action and show it with useActionState. Don't throw for these.

Uncaught exceptions are bugs or outages. Throw, and let an error boundary catch them:

  • error.tsx in a segment wraps it in an error boundary. It must be a Client Component and receives error (the thrown value, typed unknown) and retry, which re-fetches and re-renders the segment. Errors bubble up to the nearest error.tsx, so the parent layout and navigation keep working.
  • Errors from Server Components reach the client with the message stripped in production; use error.digest to match server logs.
  • global-error.tsx handles errors in the root layout and must render its own <html> and <body>.
  • catchError from next/error creates reusable component-level error boundaries.
  • Error boundaries don't catch errors in event handlers; handle those with try/catch and state.

Not found: call notFound() from next/navigation when a resource doesn't exist. It renders the nearest not-found.tsx and sets a 404 status. app/not-found.tsx also handles unmatched URLs.

Example

// app/dashboard/error.tsx
'use client'                                    // error boundaries are Client Components

import { useEffect } from 'react'

export default function DashboardError({ error, retry }: { error: unknown; retry: () => void }) {
  useEffect(() => {
    console.error(error)                        // report to your error tracker
  }, [error])
  const message = error instanceof Error ? error.message : 'Unknown error'
  return (
    <div>
      <h2>Something went wrong: {message}</h2>
      <button onClick={() => retry()}>Try again</button>
    </div>
  )
}

// app/blog/[slug]/page.tsx
// const post = await getPost(slug)
// if (!post) notFound()                         -> renders app/blog/[slug]/not-found.tsx

// app/blog/[slug]/not-found.tsx
// export default function NotFound() { return <p>No such post.</p> }

Remember

  • Return expected errors as values; throw only for unexpected failures
  • error.tsx is a client error boundary per segment with error and retry
  • notFound() renders the nearest not-found.tsx with a 404 status

Watch out for

  • Writing error.tsx without 'use client', which fails because error boundaries need client-side React.

Check yourself

  1. A widget in /dashboard/analytics throws during rendering and only app/dashboard/error.tsx exists. What does the user see?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/getting-started/error-handling