Facebook Pixel

Error Boundaries

In this tutorial, you will learn how Error Boundaries work in React 19, why they exist, and how they fit together with Suspense and the use API. Error boundaries are the safety net of modern React applications.


What We Will Learn

  • What an error boundary is
  • What kinds of errors they catch
  • How error boundaries work with use and Suspense
  • Where error boundaries should live
  • Common mistakes and best practices

By the end, you will know exactly how to protect your app from crashes without cluttering your components.


What Is an Error Boundary

An error boundary is a component that catches runtime errors during rendering and shows a fallback UI instead of crashing the entire app.

Think of it like this:

If something goes wrong in this part of the UI, show a safe message instead of breaking everything.


Why Error Boundaries Exist

Without error boundaries:

  • A single rendering error crashes the whole app
  • Users see a blank screen
  • Debugging becomes painful

Error boundaries allow React to:

  • Isolate failures
  • Recover gracefully
  • Keep the rest of the UI working

Error Boundaries in React 19

In React 19 (and modern frameworks like App Router), error boundaries are file based.

You do not manually write class components anymore.

Instead, you create an error.tsx file.


Basic Error Boundary Example

Error Boundary File

// app/error.tsx
"use client"
 
export default function Error({
  error,
  reset
}: {
  error: Error
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
 
      <button onClick={reset}>
        Try again
      </button>
    </div>
  )
}

This file automatically becomes an error boundary for the route.


What This Error Boundary Catches

An error boundary catches:

  • Errors thrown during render
  • Errors thrown by use(promise)
  • Errors thrown by server components
  • Errors thrown while streaming UI

It does not catch:

  • Event handler errors
  • Async errors outside render
  • Errors in timers or effects

Those should be handled locally.


Error Boundaries and use

When using the use API, errors are expected and intentional.

Example:

// lib/get-user.ts
export async function getUser() {
  const res = await fetch("/api/user")
 
  if (!res.ok) {
    throw new Error("Failed to load user")
  }
 
  return res.json()
}
// components/user-profile.tsx
import { use } from "react"
import { getUser } from "../lib/get-user"
 
export function UserProfile() {
  const user = use(getUser())
 
  return <p>{user.name}</p>
}

If getUser() fails:

  • The Promise rejects
  • React throws the error
  • The nearest error boundary renders

No try/catch needed.


Error Boundaries vs Suspense

This is a common source of confusion.

Feature Purpose
Suspense Handles loading states
Error Boundary Handles failures

They work together but solve different problems.


Using Suspense and Error Boundaries Together

A typical structure looks like this:

// app/page.tsx
import { Suspense } from "react"
import { UserProfile } from "../components/user-profile"
 
export default function Page() {
  return (
    <>
      <title>Error Boundaries</title>
      <meta name="description" content="React 19 error boundary tutorial" />
 
      <Suspense fallback={<p>Loading user...</p>}>
        <UserProfile />
      </Suspense>
    </>
  )
}
  • While loading: Suspense fallback
  • On failure: error boundary UI
  • On success: real content

Each concern is cleanly separated.


Resetting an Error Boundary

React provides a reset function to retry rendering.

This is useful for:

  • Temporary network failures
  • Retry buttons
  • Recoverable errors
<button onClick={reset}>
  Retry
</button>

Calling reset re-attempts rendering from scratch.


Where Error Boundaries Should Live

Good places:

  • Route level
  • Feature level
  • Data heavy sections

Avoid:

  • Wrapping everything in one global error boundary
  • Catching errors too low in the tree unless necessary

Error boundaries should match logical failure zones.


Common Mistakes

Trying to Catch Errors with try/catch

try {
  const data = use(fetchData())
} catch {
  // This will not work
}

Rendering errors must be handled by error boundaries, not try/catch.


Using Error Boundaries for Loading

Error boundaries are not for loading UI.

Use Suspense for loading and error boundaries for failures.


Mental Model

Think of error boundaries like circuit breakers:

  • Something breaks
  • The breaker trips
  • Power is cut only to that section
  • The rest of the system keeps running

This keeps your app resilient.


When You Should Use Error Boundaries

You should always have error boundaries when:

  • Using use for data fetching
  • Streaming UI
  • Rendering server components
  • Building production apps

They are not optional in modern React.


Conclusion

Error boundaries are the last line of defense in React 19.

They work hand in hand with Suspense and the use API to make apps:

  • More resilient
  • Easier to reason about
  • Safer in production

Once you rely on boundaries instead of defensive code everywhere, your components become simpler and more focused.