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 :
'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:
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 againNote 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:
'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.