npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-crash-guard

v1.1.0

Published

Production-grade React error boundary patterns for SaaS applications

Readme

react-crash-guard

Production-grade React error boundary patterns for SaaS applications — with global catching, feature isolation, async error bridging, and pluggable error reporting.

npm version CI Coverage TypeScript License: MIT

npm Package · npm Profile · GitHub


The Problem

React's default error behavior crashes your entire application on a single render failure. In production SaaS apps, this means one broken widget takes down the whole page — a terrible user experience.

Most teams either:

  • Skip error boundaries entirely (hoping for the best)
  • Add a single top-level boundary with a generic "Something went wrong" message
  • Have no visibility into what errors actually occurred in production

This repository demonstrates how to architect error handling the right way: layered, isolated, observable, and recoverable.


Architecture

The Boundary Hierarchy

The core pattern is a three-layer boundary system. Each layer serves a distinct purpose and catches a different class of failure.

┌─────────────────────────────────────────────────────────────────┐
│                     Application Root                            │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │              GlobalErrorBoundary                          │  │
│  │         (catches everything that escapes below)           │  │
│  │                                                           │  │
│  │  ┌─────────────────┐    ┌─────────────────────────────┐   │  │
│  │  │ RouteErrorBound │    │     RouteErrorBoundary      │   │  │
│  │  │    /dashboard   │    │         /settings           │   │  │
│  │  │                 │    │                             │   │  │
│  │  │ ┌─────────────┐ │    │  ┌──────────┐ ┌─────────┐   │   │  │
│  │  │ │FeatureBound │ │    │  │FeatureBnd│ │FeatBnd  │   │   │  │
│  │  │ │  <Chart />  │ │    │  │ <Form /> │ │<Table/> │   │   │  │
│  │  │ └─────────────┘ │    │  └──────────┘ └─────────┘   │   │  │
│  │  └─────────────────┘    └─────────────────────────────┘   │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Layer 1 — GlobalErrorBoundary: The last line of defence. Catches anything that wasn't isolated below. Shows a full-page fallback with recovery options.

Layer 2 — RouteErrorBoundary: Per-route isolation. A crash in /dashboard doesn't affect /settings. The user can navigate away without a full reload.

Layer 3 — FeatureErrorBoundary: Finest-grained isolation. A broken chart or widget fails silently or shows an inline fallback — the rest of the page continues working.


Error Flow & Reporting Pipeline

  React Component throws
          │
          ▼
  Nearest Error Boundary
  catches in componentDidCatch
          │
          ├──► errorClassifier(error)
          │         │
          │         └──► { type, recoverable, userMessage, retryable }
          │
          ├──► ErrorReporter.report(error, context)
          │         │
          │         ├──► SentryReporter  ──► Sentry.captureException()
          │         ├──► ConsoleReporter ──► console.error (dev)
          │         └──► CustomReporter  ──► your own pipeline
          │
          └──► Render fallback UI
                    │
                    └──► useErrorRecovery()
                              │
                              └──► retry() / reset() / maxRetries check

Async Error Bridging

React error boundaries only catch render-time errors. This hook bridges async errors (fetch failures, setTimeout throws, promise rejections) into the nearest boundary:

  async function fetchData() {
    throw new Error('Network failure')    ← NOT caught by boundary natively
  }

  const throwError = useErrorHandler()   ← bridges async → boundary

  useEffect(() => {
    fetchData().catch(throwError)         ← NOW caught by nearest boundary
  }, [])
  Component           useErrorHandler        ErrorBoundary
      │                     │                     │
      │──── throwError ─────►                     │
      │     (async err)      │                     │
      │                      │──── setState ──────►│
      │                      │     triggers        │
      │                      │     re-render       │
      │                      │     with error ────►│ componentDidCatch
      │                      │                     │ → report → fallback

Installation

npm install react-crash-guard
# or
yarn add react-crash-guard
# or
pnpm add react-crash-guard

Peer dependencies: React 18+ is required. If you use SentryReporter, also install @sentry/react >=7:

npm install @sentry/react

Quick Start

1. Wrap your app with GlobalErrorBoundary

// src/main.tsx
import { GlobalErrorBoundary, SentryReporter } from 'react-crash-guard';

const reporter = new SentryReporter({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  environment: import.meta.env.MODE,
});

root.render(
  <GlobalErrorBoundary
    reporter={reporter}
    fallback={(error, reset) => (
      <CrashPage error={error} onReset={reset} />
    )}
  >
    <App />
  </GlobalErrorBoundary>
);

2. Isolate routes with RouteErrorBoundary

// src/router.tsx
import { RouteErrorBoundary } from 'react-crash-guard';

function AppRouter() {
  return (
    <Routes>
      <Route
        path="/dashboard"
        element={
          <RouteErrorBoundary routeName="dashboard" reporter={reporter}>
            <DashboardPage />
          </RouteErrorBoundary>
        }
      />
      <Route
        path="/settings"
        element={
          <RouteErrorBoundary routeName="settings" reporter={reporter}>
            <SettingsPage />
          </RouteErrorBoundary>
        }
      />
    </Routes>
  );
}

3. Isolate risky features with FeatureErrorBoundary

// Wrap any component that might fail independently
import { FeatureErrorBoundary } from 'react-crash-guard';

function Dashboard() {
  return (
    <div>
      <FeatureErrorBoundary featureName="revenue-chart" reporter={reporter}>
        <RevenueChart />           {/* crash here stays contained */}
      </FeatureErrorBoundary>

      <FeatureErrorBoundary featureName="activity-feed" silent>
        <ActivityFeed />           {/* silent mode — no UI fallback shown */}
      </FeatureErrorBoundary>
    </div>
  );
}

4. Handle async errors with useErrorHandler

import { useErrorHandler } from 'react-crash-guard';

function CampaignList() {
  const throwError = useErrorHandler();

  useEffect(() => {
    fetchCampaigns()
      .then(setCampaigns)
      .catch(throwError); // bridges into nearest boundary
  }, []);

  return <>{/* render campaigns */}</>;
}

5. Build recovery UI with useErrorRecovery

import { useErrorRecovery } from 'react-crash-guard';

function ErrorFallback({ error }: { error: Error }) {
  const { retry, retryCount, isRecovering } = useErrorRecovery({
    maxRetries: 3,
    retryDelay: 1000,
  });

  return (
    <div>
      <p>{error.message}</p>
      <p>Retry attempt: {retryCount} / 3</p>
      <button onClick={retry} disabled={isRecovering}>
        {isRecovering ? 'Retrying...' : 'Try again'}
      </button>
    </div>
  );
}

Error Classification

The errorClassifier utility inspects errors and returns structured metadata for smarter fallback decisions:

import { classifyError } from 'react-crash-guard';

const result = classifyError(error);
// {
//   type: 'network' | 'chunk-load' | 'render' | 'permission' | 'unknown'
//   recoverable: true,
//   retryable: true,
//   userMessage: 'Connection issue. Please check your network and try again.'
// }

| Error Type | Recoverable | Retryable | Example | |---------------|-------------|-----------|-------------------------------------------| | network | ✅ | ✅ | fetch() timeout, DNS failure | | chunk-load | ✅ | ✅ | Lazy-loaded route fails after deploy | | permission | ❌ | ❌ | Unauthorized access to resource | | render | ✅ | ✅ | Component throws during render | | unknown | ❌ | ❌ | Unclassified / unexpected error |


Error Reporters

Reporters are pluggable. Implement the ErrorReporter interface to integrate any observability tool:

interface ErrorReporter {
  report(error: Error, context: ErrorContext): void | Promise<void>;
}

interface ErrorContext {
  componentStack?: string;
  boundaryName?: string;
  routeName?: string;
  userId?: string;
  extra?: Record<string, unknown>;
}

Built-in: SentryReporter

import { SentryReporter } from 'react-crash-guard';

const reporter = new SentryReporter({
  dsn: 'https://[email protected]/project',
  environment: 'production',
});

Built-in: ConsoleReporter

import { ConsoleReporter } from 'react-crash-guard';

// Useful for development and testing
const reporter = new ConsoleReporter();

Custom Reporter Example

// Send errors to your own API
class DatadogReporter implements ErrorReporter {
  report(error: Error, context: ErrorContext) {
    datadogLogs.logger.error(error.message, { error, ...context });
  }
}

API Reference

GlobalErrorBoundary

| Prop | Type | Default | Description | |---------------|---------------------------------------------------|-------------|--------------------------------------------------| | children | ReactNode | required | Application tree to wrap | | fallback | ReactNode \| (error, reset) => ReactNode | built-in UI | Custom fallback UI | | reporter | ErrorReporter | none | Pluggable error reporter | | onError | (error: Error, info: ErrorInfo) => void | none | Additional error callback | | showDialog | boolean | false | Wraps the fallback UI in a <div data-error-boundary-dialog> container |

Two types are exported to cover both use cases:

ErrorFallbackProps — props shape for a custom fallback component:

import type { ErrorFallbackProps } from 'react-crash-guard';

const CrashPage = ({ error, reset }: ErrorFallbackProps) => (
  <div>
    <p>{error.message}</p>
    <button onClick={reset}>Try again</button>
  </div>
);

<GlobalErrorBoundary fallback={(error, reset) => <CrashPage error={error} reset={reset} />}>
  <App />
</GlobalErrorBoundary>

ErrorFallback — type of the fallback prop itself, for wrapper components that forward it:

import type { ErrorFallback } from 'react-crash-guard';

interface WidgetProps {
  fallback: ErrorFallback; // ReactNode | ((error, reset) => ReactNode)
}

When showDialog={true}, the fallback content is wrapped in a <div data-error-boundary-dialog> instead of rendered bare. This does not apply built-in modal styles — it gives you a stable hook to style the fallback as an overlay dialog via CSS, and a reliable selector for E2E tests:

<GlobalErrorBoundary showDialog fallback={<CrashPage />}>
  <App />
</GlobalErrorBoundary>
/* position the fallback as a centered modal */
[data-error-boundary-dialog] {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
}
// stable selector in Playwright / Testing Library
await page.locator('[data-error-boundary-dialog]').waitFor();

Omit showDialog if you want the fallback to replace the broken subtree inline (the default behaviour).

RouteErrorBoundary

| Prop | Type | Default | Description | |---------------|---------------------------------------------------|-------------|--------------------------------------------------| | routeName | string | required | Route identifier (used in error context) | | isolate | boolean | false | Prevent error from bubbling to global boundary | | + all GlobalErrorBoundary props |

FeatureErrorBoundary

| Prop | Type | Default | Description | |---------------|---------------------------------------------------|-------------|--------------------------------------------------| | featureName | string | required | Feature identifier (used in error context) | | silent | boolean | false | Suppress UI fallback, report only | | + all GlobalErrorBoundary props |

useErrorHandler()

Returns (error: Error) => void. Call the returned function from async code to bridge errors into the nearest boundary.

useErrorRecovery(options?)

| Option | Type | Default | Description | |---------------|----------|---------|---------------------------------| | maxRetries | number | 3 | Maximum retry attempts | | retryDelay | number | 1000 | Delay between retries (ms) |

Returns { retry, retryCount, isRecovering }.


Examples

Each example is a standalone Vite app.

# Clone the repo
git clone https://github.com/mughalhere/react-crash-guard.git
cd react-crash-guard
pnpm install

# Run a specific example
cd examples/01-basic-boundary && pnpm dev
cd examples/02-global-app-boundary && pnpm dev
cd examples/03-sentry-integration && pnpm dev

# Run the full interactive demo
cd demo && pnpm dev

| Example | What it demonstrates | |---|---| | 01-basic-boundary | Single component isolation, custom fallback, reset | | 02-global-app-boundary | Three-layer hierarchy, route isolation, error propagation | | 03-sentry-integration | SentryReporter, async error bridging, production setup |


Contributing

Contributions are welcome. Please open an issue before submitting a PR for significant changes.

pnpm test          # run tests
pnpm typecheck     # tsc --noEmit
pnpm build         # build core package

License

MIT © Muhammad Zia