catchError()

Wraps a fallback into an error boundary component with retry and reset for component-level recovery.

Signature
const ErrorWrapper = catchError(fallback)
  • Added in Next.js v16.3.0

Parameters

ParameterTypeDescription
$fallback(props, errorInfo: ErrorInfo) => React.ReactNodeRenders the error UI. Receives the wrapper's props (excluding children) as its first argument and an ErrorInfo object as its second. Must be a Client Component or live in a 'use client' module.
$errorInfo.errorErrorThe error instance that was caught.
$errorInfo.retry() => voidRe-fetches and re-renders the boundary's children. On success the fallback is replaced by the re-rendered result. Prefer this over reset.
$errorInfo.reset() => voidClears the error state and re-renders without re-fetching, so it will not recover from a Server Component error.

Return value

React componentA React component that accepts the same props as the fallback's first argument plus children, wraps children in an error boundary, and renders the fallback when an error is caught.

Changelog

  • PHP v16.3.0catchError became stable.
  • PHP v16.2.0unstable_catchError introduced.

catchError() turns a fallback function into a reusable error boundary component. Where error.js is bound to a route segment and takes down everything below it, this wraps any slice of the tree you choose — one widget, one column, one tab — so a failing chart does not blank the page around it.

Two behaviours make it worth preferring over a hand-rolled React boundary. It knows about framework control-flow errors, so a redirect() or notFound() thrown inside children passes through instead of being displayed as a crash. And the error state clears automatically on a client navigation to another route.

retry versus reset

Use retry() almost always: it re-fetches and re-renders the children inside a Transition, so Client Component state outside the boundary survives. reset() only clears the error flag without re-fetching, which cannot recover a Server Component failure — the same data would throw again.

The fallback must be a Client Component. Props on the wrapper are forwarded to the fallback's first argument, which is how one boundary serves several call sites with different titles or messages.

Example

Example · typescript
'use client'

import { catchError, type ErrorInfo } from 'next/error'

function ErrorFallback(props: { title: string }, { error, retry }: ErrorInfo) {
  return (
    <div>
      <h2>{props.title}</h2>
      <p>{error.message}</p>
      <button onClick={() => retry()}>Try again</button>
    </div>
  )
}

export default catchError(ErrorFallback)

When to use it

  • Keep a failing dashboard widget from taking down the whole page
  • Give a data table its own Try again button without a route-level error.js
  • Build one reusable error boundary and vary its heading through props
  • Scope error recovery to a tab panel so the surrounding tab state survives

Frequently asked questions

What is the difference between catchError and error.js?

error.js is a file convention scoped to a route segment and replaces everything below it, while catchError creates a component you can place anywhere to catch errors in just that part of the tree. Both give you retry and reset; catchError also takes props, so one fallback can serve several boundaries.

Should I use retry or reset in a Next.js error boundary?

Use retry() in almost every case: it re-fetches and re-renders the children inside a Transition, preserving Client Component state outside the boundary. reset() only clears the error state without re-fetching, so it cannot recover from a Server Component error.

Does catchError catch redirect() and notFound()?

No, and that is deliberate. Those APIs work by throwing framework control-flow errors, and catchError lets them pass through to Next.js instead of rendering them as a crash — which is the main advantage over a hand-written React error boundary.

More examples

Using the wrapper around part of the tree

One fallback definition serves two boundaries, each given a different heading through the title prop that catchError forwards to the fallback's first argument. If the Revenue query fails, only that block is replaced by the error UI — the page heading and the Signups panel keep rendering, which an error.js at this segment could not do.

Example · typescript
// app/dashboard/page.tsx
import { Suspense } from 'react'
import ErrorWrapper from '../custom-error-boundary'
import { Revenue } from './revenue'
import { Signups } from './signups'

export default function DashboardPage() {
  return (
    <main>
      <h1>Dashboard</h1>

      <ErrorWrapper title="Revenue unavailable">
        <Suspense fallback={<p>Loading revenue...</p>}>
          <Revenue />
        </Suspense>
      </ErrorWrapper>

      <ErrorWrapper title="Signups unavailable">
        <Suspense fallback={<p>Loading signups...</p>}>
          <Signups />
        </Suspense>
      </ErrorWrapper>
    </main>
  )
}

Offering both retry and reset

Try again re-fetches and re-renders the children inside a Transition, so Client Component state outside the boundary is preserved and a transient server failure can genuinely recover. Reset only clears the error flag: for an error thrown by a Server Component the same render would fail again immediately, which is why retry is the default choice.

Example · typescript
// app/custom-error-boundary.tsx
'use client'

import { catchError, type ErrorInfo } from 'next/error'

function ErrorFallback(props: {}, { error, retry, reset }: ErrorInfo) {
  return (
    <div role="alert">
      <p>{error.message}</p>
      <button onClick={() => retry()}>Try again</button>
      <button onClick={() => reset()}>Reset</button>
    </div>
  )
}

export default catchError(ErrorFallback)

A server-rendered fallback passed as a prop

The fallback function stays a Client Component while the UI it shows is a Server Component passed in as a ReactNode prop, which is how you get data-driven error copy. The cost is that SupportNotice renders on every page render, error or not, so a plain client-side fallback is the better default.

Example · typescript
// app/error-boundary.tsx
'use client'

import { catchError, type ErrorInfo } from 'next/error'

function ErrorFallback(
  props: { fallback: React.ReactNode },
  errorInfo: ErrorInfo
) {
  return props.fallback
}

export default catchError(ErrorFallback)

// app/some-component.tsx
import ErrorBoundary from '../error-boundary'

async function SupportNotice() {
  const data = await getSupportStatus()
  return <div>{data.message}</div>
}

export default function Component({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <ErrorBoundary fallback={<SupportNotice />}>{children}</ErrorBoundary>
  )
}

Discussion

  • Be the first to comment on this lesson.