Frontend
medium
mid
How would you structure error boundaries to be per-page and 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
/ordersshows 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 byWidgetErrorBoundary→ chart shows a small fallback, page is fine. - An error elsewhere in
DashboardPage→ caught byPageErrorBoundary→ page shows an error, app shell fine. - An error in
AppLayoutitself → onlyRootErrorBoundaryis 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-boundaryforresetKeys/onErrorrather 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.