Skip to main content
Saved

Error Boundary

Category Resilience

Level Advanced

Examples

React, Vue, Svelte, Native web

Catch JavaScript errors in component trees and display fallback UI gracefully.

In this article
  1. Problem
  2. Solution
  3. Example
  4. Basic Error Boundary
  5. Granular Boundaries
  6. Custom Fallback UI
  7. Using react-error-boundary
  8. Benefits
  9. Tradeoffs
  10. Summary
  11. See also

Error Boundary

Problem

A simple typo in a sidebar widget, a malformed API response, or a third-party library throwing an unexpected exception can unmount your entire component tree and leave users staring at a blank screen.

I’ve seen users lose entire forms, along with everything they’d typed, because an unrelated chart component failed to render. The browser console shows a helpful stack trace for developers, but users see nothing useful.

This happens because errors bubble up through your component tree until they hit the root, at which point everything crashes together. One broken footer widget shouldn’t take down the entire page, but without error boundaries it does.

Solution

Think of error boundaries as try-catch blocks for your component tree. You wrap sections of your UI in these special components that catch render errors and display fallback UI instead of crashing.

Everything outside the boundary keeps working. If your sales chart throws an error, users can still see the navigation and sidebar because you’ve contained the blast radius to the chart area.

I recommend a layered approach: put an error boundary at the app root as a safety net, then add granular boundaries around risky features like dashboard widgets, third-party integrations, and user-generated content.

One caveat: React’s error boundaries only catch errors during rendering, lifecycle methods, and constructors. They won’t catch event handlers, async code, or promise rejections, and Svelte’s boundaries have the same limits. Vue’s onErrorCaptured also sees errors from event handlers and watchers that Vue calls, but code in a setTimeout or a promise nobody awaits still needs its own try-catch.

Example

React and Vue have built-in hooks for this, and Svelte 5 has the <svelte:boundary> element. Web Components have no equivalent, because the browser gives custom elements no way to catch errors thrown while their children render, so that version relies on children reporting their own errors.

Basic Error Boundary

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught:', error, errorInfo);
  }

  resetError = () => this.setState({ hasError: false, error: null });

  render() {
    if (this.state.hasError) {
      const { fallback } = this.props;
      if (typeof fallback === 'function') {
        return fallback(this.state.error, this.resetError);
      }
      return fallback ?? (
        <div className="error-boundary">
          <h1>Something went wrong</h1>
          <button onClick={this.resetError}>Try again</button>
        </div>
      );
    }
    return this.props.children;
  }
}

function App() {
  return (
    <ErrorBoundary>
      <Dashboard />
    </ErrorBoundary>
  );
}

Granular Boundaries

Wrapping individual widgets isolates failures so one broken component doesn’t take down the entire dashboard:

function Dashboard() {
  return (
    <ErrorBoundary fallback={<PageError />}>
      <ErrorBoundary fallback={<WidgetError />}>
        <SalesChart />
      </ErrorBoundary>
      <ErrorBoundary fallback={<WidgetError />}>
        <RecentActivity />
      </ErrorBoundary>
      <ErrorBoundary fallback={<WidgetError />}>
        <UserList />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}

Custom Fallback UI

A render prop pattern allows tailored fallback UI with access to the error and reset function:

<ErrorBoundary
  fallback={(error, reset) => (
    <div>
      <h2>Widget failed to load</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  )}
>
  <ComplexWidget />
</ErrorBoundary>

Using react-error-boundary

The react-error-boundary library provides a functional API so you don’t need to write class components:

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

<ErrorBoundary FallbackComponent={ErrorFallback} onError={logErrorToService}>
  <Dashboard />
</ErrorBoundary>

Benefits

  • Your application stays partially functional when things break. Users can keep using navigation and unaffected features even when one component fails.
  • Users see helpful fallback UI instead of a blank screen; “This widget failed to load” is much better than a blank white page.
  • You contain the blast radius, so a broken chart doesn’t take down the entire dashboard.
  • With the boundary connected to Sentry or another monitoring tool, errors are logged automatically and you hear about production errors as they happen.
  • Retry buttons let users recover without refreshing the page, which helps with transient failures that only need a second attempt.
  • You can show friendly messages to users while logging detailed stack traces for developers.

Tradeoffs

  • Error boundaries mostly catch errors during rendering and lifecycle methods. Event handlers (in React and Svelte), async code, and promise rejections still need regular try-catch.
  • In React, error boundaries require class components, though react-error-boundary lets you avoid writing classes yourself.
  • Generic “Something went wrong” messages can hide bugs from the team, so the boundary needs to log enough detail to debug them.
  • Placement is tricky: too few boundaries means large sections crash together, too many means fallback UI everywhere.
  • Reset needs care, because if you clear the error state without fixing the cause, the same error might happen again straight away.

Summary

Error boundaries catch JavaScript errors in component trees and display fallback UI instead of crashing the entire application. With boundaries placed around risky code, a failure stays in one small part of the page and the rest keeps working. In production, that means one broken widget shows a fallback instead of the whole page going blank.

See also

Newsletter

A Monthly Email
from Den Odell

Behind-the-scenes thinking on frontend patterns, site updates, and more

No spam. Unsubscribe anytime.