Without a root-level error boundary, a single thrown exception in any React component silently blanks the entire application — users see a white screen with no explanation and no recovery path. CWE-755 (Improper Handling of Exceptional Conditions) names this directly: the absence of fault tolerance at the application boundary turns transient bugs into total outages. For Next.js App Router projects, a route-level error.tsx alone is insufficient — errors in the root layout bypass it entirely, requiring global-error.tsx as a separate boundary. ISO 25010 reliability.fault-tolerance classifies the absence of this pattern as a direct reliability failure.
Critical because a missing root boundary converts any unhandled render exception into a full application crash with no user-visible recovery path.
Add both app/global-error.tsx and app/error.tsx for Next.js App Router — the global file is mandatory because route-level boundaries don't cover root layout errors.
// app/global-error.tsx
'use client'
export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
return (
<html>
<body>
<h2>Something went wrong</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
For non-Next.js React apps, wrap the root render point with react-error-boundary. Verify by intentionally throwing in a component during development and confirming the fallback UI appears instead of a blank page.
ID: saas-error-handling.error-boundaries.react-error-boundary
Severity: critical
What to look for: Examine whether the application has a top-level error boundary covering the main component tree. For Next.js App Router: check for src/app/error.tsx or app/error.tsx (the route-level error boundary) AND src/app/global-error.tsx or app/global-error.tsx (the root layout error boundary). For Next.js Pages Router: check for pages/_error.tsx. For other React apps (Vite/CRA/Remix/Gatsby): look for a component that extends React.Component and implements componentDidCatch/getDerivedStateFromError, or imports from react-error-boundary. Check whether the boundary wraps the root render point (e.g., <App /> in main.tsx or equivalent).
Pass criteria: Count all error boundary files in the project (error.tsx, global-error.tsx, _error.tsx, ErrorBoundary components). Pass if a root-level error boundary exists and wraps the primary application tree. For Next.js App Router, pass if both error.tsx (for route errors) and global-error.tsx (for root layout errors) are present — at least 2 boundary files required. For Pages Router, pass if pages/_error.tsx exists. For other React apps, pass if an ErrorBoundary component wraps <App /> or the equivalent root component. Report even on pass: "Found X error boundary files covering Y route segments."
Fail criteria: Fail if no error boundary exists at the application root. For Next.js App Router, fail if global-error.tsx is absent (route-level error.tsx alone is insufficient — it doesn't catch errors in the root layout). Do NOT pass when only a route-level error.tsx exists without global-error.tsx — root layout errors will crash the entire app with no fallback.
Cross-reference: For error boundary fallback UI quality, see the error-boundaries-fallback-ui check in this audit.
Skip (N/A) when: The project has no React dependency. Signal: react absent from package.json dependencies/devDependencies, no .jsx/.tsx files present, and framework is not Next.js/Remix/Gatsby/Vite-React.
Detail on fail: Identify which boundary type is missing (e.g., "global-error.tsx not found; route-level error.tsx exists but does not catch root layout errors" or "No ErrorBoundary wrapping root component in main.tsx"). Max 500 chars.
Remediation: Your application has no safety net for unexpected rendering errors. When any component throws, the entire page goes blank with no user feedback.
For Next.js App Router, create both files:
// app/global-error.tsx
'use client'
export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
return (
<html>
<body>
<h2>Something went wrong</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
// app/error.tsx
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
For non-Next.js React apps, wrap your root render with react-error-boundary:
import { ErrorBoundary } from 'react-error-boundary'
<ErrorBoundary fallback={<div>Something went wrong. <button onClick={() => window.location.reload()}>Reload</button></div>}>
<App />
</ErrorBoundary>
Verify by intentionally throwing in a component and confirming the fallback UI renders instead of a blank page.