Error Boundaries

Error Boundaries with error.js and catchError

An error.js wraps its segment in a React 7,897 error boundary. It must be a Client Component — error boundaries only run in the browser — and it receives the error plus a retry function, stable since Next.js 16.3 10,514 :

app/broken/error.jsJavaScript
'use client'
export default function BrokenError({ error, retry }) {
  return <div id="broken-error">
    <h2>That section failed to load</h2>
    <p>{error.message}</p><p>digest: {error.digest}</p>
    <button onClick={() => retry()}>Try again</button>
  </div>
}

app/broken/page.js throws on every request. A click from the home page lands in the boundary, leaving the layout untouched:

Output of 16
url  : http://localhost:3311/broken
nav  : Home | Blog
main : That section failed to load |  | Minified React error #441; visit
       https://react.dev/errors/441 ... |  | digest: 3702639673 |  | Try again

Note what the message is not. The server threw new Error('The pricing service is unreachable'); the browser got a generic React message, because a production build refuses to ship a Server Component's error text to the client — that text routinely carries connection strings. What crosses is error.digest, a hash of the original error, 3702639673 here, which also appears in the server log, so a ticket quoting the digest points at one stack trace. In next dev the real message is forwarded instead.

retry() re-fetches and re-renders the boundary's children inside a transition, preserving client state outside it: the right answer to a timeout or a flaky upstream. The older reset() only clears the error state without re-fetching, so it cannot recover from a Server Component failure.

Like loading.js, error.js sits at a fixed place in the hierarchy. It wraps loading.js, not-found.js, page.js and everything nested below, but not the layout.js or template.js of its own segment, so a throw in app/dashboard/layout.js flies past app/dashboard/error.js into the parent's boundary; put an error.js above any layout that can fail. An error thrown after streaming began still renders the boundary — only that section is replaced, and the response stays 200.

When the unit that can fail is a component rather than a route, Next.js 16.3 added catchError from next/error, which builds a boundary from a fallback function and goes anywhere in the tree:

app/_lib/widget-error.jsJavaScript
'use client'
import { catchError } from 'next/error'
function Fallback(props, { error, retry }) {
  return <aside>{props.title} failed: {error.message}
    <button onClick={() => retry()}>Retry</button></aside>
}
export default catchError(Fallback)

<WidgetError title="Revenue"><Revenue /></WidgetError> then isolates one dashboard tile. Unlike a hand-written React boundary, catchError knows about the framework: it lets the throws behind redirect() and notFound() pass through, and clears itself on navigation to another route. Do not wrap an error.js default export in it — that file already renders inside a boundary Next.js provides.