The Problem
Without error boundaries, a JavaScript error in any component crashes your entire React app and shows a blank screen. Users have no idea what happened.
TypeError: Cannot read properties of null (reading 'name')
at UserCard (UserCard.tsx:12)
at ...
The entire app unmounts. Every user on that page sees a blank white screen.
What Error Boundaries Do
An error boundary is a React component that:
- Catches JavaScript errors from its child component tree
- Renders a fallback UI instead of the crashed component
- Lets the rest of the app continue working
It's like a try/catch, but for React component rendering.
Creating an Error Boundary
Error boundaries must be class components (as of React 18 โ useErrorBoundary hook is still experimental in most setups):
import { Component, type ReactNode } from 'react'
interface Props {
children: ReactNode
fallback?: ReactNode
onError?: (error: Error, info: { componentStack: string }) => void
}
interface State {
hasError: boolean
error: Error | null
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: { componentStack: string }) {
// Log to your error tracking service
this.props.onError?.(error, info)
console.error('Error caught by boundary:', error, info)
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="error-fallback">
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
)
}
return this.props.children
}
}
Using Error Boundaries
Protect the entire app
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
<ErrorBoundary fallback={<AppErrorPage />}>
{children}
</ErrorBoundary>
</body>
</html>
)
}
Protect individual sections
Better: use multiple granular boundaries so one broken widget doesn't take down the page:
export default function Dashboard() {
return (
<div>
<ErrorBoundary fallback={<WidgetError name="Analytics" />}>
<AnalyticsWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Recent Activity" />}>
<ActivityFeed />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="User Stats" />}>
<UserStats />
</ErrorBoundary>
</div>
)
}
If AnalyticsWidget throws, only that widget shows an error. The rest of the dashboard continues working.
Next.js Error Boundaries: error.tsx
In Next.js App Router, create error.tsx next to page.tsx โ it automatically becomes the error boundary for that route segment:
// app/dashboard/error.tsx
'use client' // Error boundaries must be client components
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Dashboard failed to load</h2>
<p>{error.message}</p>
<button onClick={reset}>Retry</button>
</div>
)
}
The reset function re-renders the route segment โ no full page reload needed.
What Error Boundaries Don't Catch
Error boundaries only catch errors during rendering and lifecycle methods. They don't catch:
// โ Not caught: async errors in event handlers
<button onClick={async () => {
throw new Error('not caught by boundary')
}}>
// โ Not caught: errors in useEffect
useEffect(() => {
throw new Error('not caught by boundary')
}, [])
// โ Not caught: errors in async components (need server error.tsx)
// โ
Caught: errors during render
function BrokenComponent() {
throw new Error('caught!')
return <div />
}
For async errors, use your own try/catch + error state, or a library like react-query which surfaces async errors in a form error boundaries can catch.
Integrating with Error Monitoring
import * as Sentry from '@sentry/react'
// Use Sentry's pre-built error boundary
<Sentry.ErrorBoundary
fallback={({ error, resetError }) => (
<ErrorPage error={error} onReset={resetError} />
)}
showDialog // Opens Sentry's user feedback dialog
>
<App />
</Sentry.ErrorBoundary>
Key Takeaways
- Error boundaries catch render errors and prevent the entire app from crashing
- Use multiple granular boundaries โ one per section/widget, not just one at the root
- In Next.js: create
error.tsxnext topage.tsxfor route-level error boundaries - Error boundaries don't catch async errors, event handler errors, or
useEffecterrors - Always connect to error monitoring (Sentry, Datadog) in
componentDidCatch