Topics
v6.0.0
On this page · sections

Error Boundaries & Sentry Integration

Error boundaries isolate render failures to a widget or route segment. Sentry captures what boundaries miss — event handlers, async code, and production stack traces with source maps.

LayerCatchesDoes not catch
Error boundaryRender-phase throws in childrenEvent handlers, async, boundary’s own render
try/catchSynchronous handler codeRender throws
Sentry globalUnhandled errors, manual captureNothing without init
error.tsx (Next.js)Route segment render errorsClient handler errors

What Error Boundaries Catch

Definition

Error boundaries catch render-phase errors in child components — thrown during render, in child lifecycle methods tied to render, or in constructors of children below the boundary.

They do not catch:

  • Event handler errors (onClick, onSubmit)
  • Async code (setTimeout, promises, useEffect async)
  • Server-side rendering errors in the Node process
  • Errors thrown in the boundary’s own render
'use client'; function BrokenList({ items }: { items: string[] }) { return ( <ul> {items.map((item) => ( <li key={item}>{item.toUpperCase()}</li> // throws if item is null ))} </ul> ); } function SaveButton() { function handleClick() { throw new Error('click failed'); // NOT caught by boundary } return <button onClick={handleClick}>Save</button>; }
Render error → boundary → fallback UI Event handler error → try/catch + Sentry.captureException Promise error → window.onerror / Sentry.captureException SSR server error → server error UI, not client boundary

Suspense handles pending state; boundaries handle thrown errors — wrap Suspense tree inside a boundary.

Interview: Name event handlers, async, and SSR server errors as non-caught cases immediately — signals production experience.

Implementing an Error Boundary

Definition

Error boundaries must be class components — getDerivedStateFromError and componentDidCatch have no hooks equivalent yet. react-error-boundary wraps this with FallbackComponent, onReset, and resetKeys.

'use client'; import { ErrorBoundary } from 'react-error-boundary'; function WidgetFallback({ resetErrorBoundary }: { resetErrorBoundary: () => void }) { return ( <div role="alert"> <p>Chart failed to load.</p> <button type="button" onClick={resetErrorBoundary}>Retry</button> </div> ); } export function DashboardChart({ data }: { data: unknown[] }) { return ( <ErrorBoundary FallbackComponent={WidgetFallback} resetKeys={[data.length]}> <ExpensiveChart data={data} /> </ErrorBoundary> ); }
import { Component, type ErrorInfo, type ReactNode } from 'react'; export class ErrorBoundary extends Component< { children: ReactNode; onError?: (error: Error, info: ErrorInfo) => void }, { hasError: boolean } > { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: Error, info: ErrorInfo) { this.props.onError?.(error, info); } render() { if (this.state.hasError) { return ( <div role="alert"> <p>Something went wrong.</p> <button type="button" onClick={() => this.setState({ hasError: false })}> Try again </button> </div> ); } return this.props.children; } }

Placement: boundary wraps from parent, not inside throwing component. Fallback UX: message, retry, link home — not raw stack trace.

Next.js error.tsx is App Router’s route-level boundary. React Query: throwOnError in suspense mode throws into boundary.

Interview: getDerivedStateFromError must be pure; side effects go in componentDidCatch.

Sentry Client Integration

Definition

// instrumentation-client.ts — Next.js App Router import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? 'development', release: process.env.NEXT_PUBLIC_SENTRY_RELEASE, tracesSampleRate: 0.1, beforeSend(event) { if (event.user) delete event.user.email; return event; }, });
'use client'; import * as Sentry from '@sentry/react'; import { ErrorBoundary } from '@sentry/react'; export function App() { return ( <ErrorBoundary fallback={<p>An error occurred</p>} showDialog> <Routes /> </ErrorBoundary> ); } // Manual capture for async/handler errors async function saveForm(data: FormData) { try { await api.save(data); } catch (error) { Sentry.captureException(error, { tags: { feature: 'checkout' } }); throw error; } }

Init once at bootstrap. Set release matching CI build version. beforeSend scrubs PII. tracesSampleRate controls performance monitoring cost.

Wrap app root + federated remote routes + critical widgets in nested boundaries. Tag events with remoteName, route, userId hash.

Interview: Boundaries for render; captureException for handlers and async — both layers required.

Source Maps & Release Tracking

Definition

Production minified stacks are unreadable without source maps. Upload maps to Sentry during CI — never serve them publicly.

// vite.config.js — hidden source maps for prod // build: { sourcemap: 'hidden' } // CI — upload after build // sentry-cli sourcemaps upload --release=$GIT_SHA ./dist
Build → embed debug IDs → upload maps to Sentry (private) Runtime error → Sentry maps minified stack → original TSX line
  • release — must match between init and map upload (GIT_SHA, package version)
  • hidden-source-map — maps exist for Sentry, not linked in browser bundle
  • Delete maps from public CDN after upload

Without release alignment, Sentry shows useless a.min.js:1:4829 stacks.

Staff: per-environment DSN, sample rates tuned by traffic, alert rules on new issues and error rate spikes per release.

Interview: Source maps uploaded to Sentry in CI — hidden from public CDN.

Micro-Frontend Error Strategy

Definition

Federated and multi-team UIs need isolated failure domains:

<RemoteErrorBoundary fallback={<CheckoutUnavailable />} remoteName="checkout"> <Suspense fallback={<Skeleton />}> <FederatedCheckout /> </Suspense> </RemoteErrorBoundary>
  • Tag Sentry events with remoteName, remoteVersion, hostVersion
  • Circuit breaker: after N failures, stop loading remote until retry
  • Degraded mode UI — not white screen on shell
  • Shared error taxonomy across teams — consistent severity levels

Chunk load failures from dynamic import() reject Promises — Error Boundary catches if thrown during render via React.lazy.

Per-remote health checks in synthetic monitoring. Rollback remote independently without host redeploy.

Staff: error budget per surface, on-call runbooks for remote CDN outage, cross-team incident channel when shared dep version skews.

Interview: Federated routes need Error Boundary + Suspense — blank shell is unacceptable in production.

Senior Interview Summary

Boundaries catch

Render-phase errors in children. Not handlers, async, SSR server, or boundary’s own render.

Implementation

Class component or react-error-boundary. Fallback + retry. resetKeys on prop change.

Sentry

Init at bootstrap. captureException for async/handlers. beforeSend scrubs PII.

Source maps

hidden-source-map in prod. Upload in CI. release must match init.

Micro-frontends

Per-remote boundaries. Tag remoteName. Degraded mode, not white screen.

Best mental model

Boundary → render isolation + fallback UX Sentry → observability for everything else Together → graceful degradation + debuggable production

Practical rules

Root boundary → catch unhandled render crashes Widget boundaries → isolate charts, federated remotes Handlers/async → try/catch + captureException Never expose stack traces → user-facing fallback only

What to say in interviews

List three things boundaries don’t catch first. Show boundary + Sentry init pattern. Explain source map upload with release alignment. For federation, describe degraded-mode UX and per-remote tagging.

Last updated on