Back to React
React
medium
mid

How would you structure error boundaries so they exist both per page and at the global level?

Nest boundaries: a global root boundary catches anything that escapes, page-level boundaries isolate each route so one page's crash doesn't blank the app, and optional widget-level boundaries contain risky sections. Inner boundaries catch first; outer is the safety net.

6 min read·~12 min to think through

Error boundaries nest — the innermost boundary above the error catches it. That property is the whole design: put boundaries at the granularities where you want isolation.

The hierarchy

ts
<RootErrorBoundary>            ← global safety net, full-page fallback
  <AppLayout>                  ← nav/shell, stays alive
    <PageErrorBoundary>        ← per-route: isolates this page
      <DashboardPage>
        <WidgetErrorBoundary>  ← optional: isolates a risky widget
          <RevenueChart />
        </WidgetErrorBoundary>
      </DashboardPage>
    </PageErrorBoundary>
  </AppLayout>
</RootErrorBoundary>

Global / root boundary

  • Role: last resort. Catches errors that escape every inner boundary (including errors in the layout/shell itself).
  • Fallback: full-page "Something went wrong" — reload, go home, report.
  • Must be robust — keep it minimal so it can't throw.

Page / route boundary

  • Role: isolation. A crash in /orders shows an error state where the page content goes; the nav, header, and the rest of the app stay interactive.
  • Fallback: in-page error with Retry and navigation.
  • Reset on route change — use resetKeys={[pathname]} so navigating to a healthy route clears the crashed state automatically.
  • Usually placed at the router outlet, one per route (or one wrapping the outlet, keyed by route).

Widget boundary (optional, targeted)

  • Role: contain a component that fails independently — a third-party embed, a chart, an experimental feature.
  • Fallback: small inline message; the rest of the page is untouched.
  • Use selectively — not around everything.

How the layering behaves

  • An error in RevenueChart → caught by WidgetErrorBoundary → chart shows a small fallback, page is fine.
  • An error elsewhere in DashboardPage → caught by PageErrorBoundary → page shows an error, app shell fine.
  • An error in AppLayout itself → only RootErrorBoundary is above it → full-page fallback.

Practical notes

  • Each layer reports to your logging service with its level as context (boundary: "page" | "widget" | "root").
  • Use react-error-boundary for resetKeys/onError rather than rewriting class boundaries.
  • Don't over-nest — three sensible levels beat ten boundaries nobody can reason about.

Follow-up questions

  • How does boundary nesting decide which one catches an error?
  • How do you auto-reset a page boundary on navigation?
  • When is a widget-level boundary worth it vs over-engineering?
  • What happens if the layout itself throws?

Common mistakes

  • Only a root boundary — zero isolation between pages.
  • Page boundary that doesn't reset on navigation, so a crashed page stays crashed.
  • Wrapping every component in its own boundary — unmanageable.
  • Root fallback that depends on app context and can itself throw.

Performance considerations

  • Inner boundaries limit re-render blast radius — only the failed subtree resets, not the whole tree. resetKeys tied to route/state enables clean recovery without full reloads.

Edge cases

  • Error in the shared layout — only the root catches it.
  • Navigating away from a crashed page (boundary must reset).
  • An error in one widget boundary's fallback bubbling up.
  • Lazy route chunk failing to load.

Real-world examples

  • React Router: errorElement at the layout route (global) and per child route (page).
  • A dashboard with per-widget boundaries so a failing third-party chart doesn't blank the page.

Senior engineer discussion

Seniors explain the nesting/catch semantics precisely and place boundaries at exactly the isolation seams that matter — root, route, and a few risky widgets — not everywhere. They cover route-change resets via resetKeys, per-level reporting context, and keeping the root fallback dependency-free so it can't fail.

Related questions