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.
| Layer | Catches | Does not catch |
|---|---|---|
| Error boundary | Render-phase throws in children | Event handlers, async, boundary’s own render |
try/catch | Synchronous handler code | Render throws |
| Sentry global | Unhandled errors, manual capture | Nothing without init |
error.tsx (Next.js) | Route segment render errors | Client 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,useEffectasync) - 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 boundarySuspense 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:
getDerivedStateFromErrormust be pure; side effects go incomponentDidCatch.
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;
captureExceptionfor 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 ./distBuild → embed debug IDs → upload maps to Sentry (private)
Runtime error → Sentry maps minified stack → original TSX linerelease— 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 productionPractical 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 onlyWhat 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.