DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Production-Ready Error Boundaries in React: Patterns for Graceful Failures

Production-Ready Error Boundaries in React: Patterns for Graceful Failures

Unhandled render errors crash your entire React tree. Error boundaries contain the damage.
Here's how to use them effectively in production.

The Problem

// If UserCard throws, the entire page goes blank
function Dashboard() {
  return (
    <div>
      <Header />
      <UserCard />  {/* if this throws, everything dies */}
      <Analytics />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Basic Error Boundary

Error boundaries must be class components (for now):

import React, { Component, ReactNode } from 'react'

interface Props {
  children: ReactNode
  fallback?: ReactNode
}

interface State {
  hasError: boolean
  error: Error | null
}

class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null }

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

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    console.error('ErrorBoundary caught:', error, info.componentStack)
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? <div>Something went wrong</div>
    }
    return this.props.children
  }
}
Enter fullscreen mode Exit fullscreen mode

Using react-error-boundary

Don't write your own — use the battle-tested library:

npm install react-error-boundary
Enter fullscreen mode Exit fullscreen mode
import { ErrorBoundary } from 'react-error-boundary'

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

function Dashboard() {
  return (
    <div>
      <Header />
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <UserCard />
      </ErrorBoundary>
      <Analytics />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Granular Boundaries

Wrap independent sections — a failure in one shouldn't affect others:

function Dashboard() {
  return (
    <div className="grid grid-cols-3">
      <ErrorBoundary FallbackComponent={WidgetError}>
        <RevenueWidget />
      </ErrorBoundary>

      <ErrorBoundary FallbackComponent={WidgetError}>
        <UsersWidget />
      </ErrorBoundary>

      <ErrorBoundary FallbackComponent={WidgetError}>
        <ActivityFeed />
      </ErrorBoundary>
    </div>
  )
}

function WidgetError({ error, resetErrorBoundary }: FallbackProps) {
  return (
    <div className="border border-red-200 rounded p-4">
      <p className="text-red-600 text-sm">Failed to load widget</p>
      <button onClick={resetErrorBoundary} className="text-sm text-gray-500 mt-2">
        Retry
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Logging to Your Error Monitoring Service

import * as Sentry from '@sentry/nextjs'

function onError(error: Error, info: { componentStack: string }) {
  Sentry.captureException(error, {
    extra: { componentStack: info.componentStack },
  })
}

<ErrorBoundary
  FallbackComponent={ErrorFallback}
  onError={onError}
>
  <Dashboard />
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

Reset on Route Change

import { useLocation } from 'react-router-dom'
import { ErrorBoundary } from 'react-error-boundary'

function App() {
  const location = useLocation()

  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      resetKeys={[location.pathname]}  // auto-reset on navigation
    >
      <Routes />
    </ErrorBoundary>
  )
}
Enter fullscreen mode Exit fullscreen mode

What Error Boundaries DON'T Catch

  • Errors in event handlers (use try/catch)
  • Errors in async code (use try/catch + state)
  • Errors during SSR
  • Errors thrown in the boundary itself
// This WON'T be caught by error boundary:
function Button() {
  const handleClick = async () => {
    try {
      await submitForm()  // async error — handle here
    } catch (err) {
      setError(err.message)
    }
  }
  return <button onClick={handleClick}>Submit</button>
}
Enter fullscreen mode Exit fullscreen mode

Route-Level Boundaries in Next.js

In Next.js App Router, use error.tsx:

// app/dashboard/error.tsx
'use client'

export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Dashboard failed to load</h2>
      <button onClick={reset}>Try again</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

This is a built-in error boundary for the entire /dashboard route segment.

Production Checklist

  • [ ] Route-level error.tsx files in Next.js
  • [ ] Granular boundaries around independent widgets
  • [ ] Error reporting wired up (Sentry / LogRocket)
  • [ ] Meaningful fallback UIs (not just white screens)
  • [ ] Reset buttons so users can recover
  • [ ] Async error handling in event handlers

The AI SaaS Starter Kit ships with Sentry integrated, route-level error boundaries configured, and all the production error handling patterns pre-wired. $99 one-time — clone and ship.

Top comments (0)