Skip to Content
ExamplesNext.js Error Handling

Next.js Error Handling

A dashboard that demonstrates error boundaries at two levels: a reusable ErrorBoundary class component that isolates failures per-widget, and Next.js error.tsx that catches unhandled page-level errors. One widget always fails — the rest of the page keeps working. A separate /broken route shows the page-level boundary in action.

How it works

The dashboard renders two widgets: weather (always throws) and news (always succeeds). Each widget is wrapped in a custom <ErrorBoundary> that catches the error and renders an inline message — the news widget is unaffected. Navigating to /broken triggers a page that throws directly. Since there’s no component-level boundary around it, the error bubbles up to error.tsx, which renders a full-page error with a retry button.

nextjs-error-handling/ ├── lib/api.ts ← data fetchers (one always throws) ├── app/layout.tsx ← root layout with navigation ├── app/error.tsx ← page-level error boundary (Next.js convention) ├── app/page.tsx ← dashboard: widgets in component-level boundaries ├── app/error-boundary.tsx ← reusable ErrorBoundary class component ('use client') ├── app/weather-widget.tsx ← async server component (always throws) ├── app/news-widget.tsx ← async server component (always succeeds) └── app/broken/page.tsx ← page that throws (caught by error.tsx)

lib/api.ts

Two data fetchers. getWeather always throws after a short delay — simulating an API that’s down. getNews always succeeds. This lets us show an error boundary catching one widget while the other renders normally.

lib/api.ts

app/layout.tsx

Root layout with a nav bar linking to both routes. <Link> from next/link handles client-side navigation so error.tsx can catch errors without a full page reload.

app/layout.tsx

app/error.tsx

Next.js page-level error boundary. When an error isn’t caught by a closer boundary, Next.js renders this component instead of the page. It must be a client component ('use client'). The framework passes the error object and a reset function — calling reset() tells Next.js to re-render the route segment, which retries the server component that threw.

app/error.tsx

app/page.tsx

The dashboard composes both widgets with the same pattern: <ErrorBoundary> on the outside catches errors, <Suspense> on the inside handles the loading state. This is the recommended ordering — each wrapper handles one concern. Suspense doesn’t catch errors (only pending states), so the error always propagates up to the nearest error boundary regardless of ordering. The weather widget throws, so its ErrorBoundary renders an inline error message. The news widget succeeds and renders normally. The page itself never throws, so error.tsx isn’t triggered here.

app/page.tsx

app/error-boundary.tsx

React error boundaries must be class components — there’s no hook equivalent. getDerivedStateFromError catches any error thrown by a child during rendering and stores it in state. When state.error is set, the boundary renders an inline error message instead of its children. This keeps the error contained — the rest of the page is unaffected. Note 'use client' — error boundaries are inherently interactive.

app/error-boundary.tsx

app/weather-widget.tsx

An async server component that always throws. When it’s wrapped in an <ErrorBoundary>, the boundary catches the error and renders an inline message — the rest of the page is unaffected. Without the boundary, this error would bubble up to error.tsx and take down the whole page.

import { getWeather } from '../lib/api' export async function WeatherWidget() { await getWeather() return null }

app/news-widget.tsx

An async server component that always succeeds. Even though the weather widget next to it throws, this component renders normally because each widget has its own <ErrorBoundary>. This is the key benefit of component-level error boundaries — failures are isolated.

app/news-widget.tsx

app/broken/page.tsx

A page that throws unconditionally. There’s no component-level <ErrorBoundary> around it, so the error bubbles up to error.tsx which renders the full-page error UI with a retry button. This demonstrates the page-level boundary — error.tsx is the safety net for any uncaught error in the route.

export default function BrokenPage(): never { throw new Error('This page crashed to demonstrate error.tsx') }

Run it

npx create-next-app@latest nextjs-error-handling --ts --app --no-tailwind --no-eslint --no-src-dir cd nextjs-error-handling mkdir -p lib app/broken # create the files above npm run dev

Open http://localhost:3000 . The weather widget shows an orange error box while the news widget loads normally — that’s the component-level boundary at work. Click “Broken Page” in the nav to see the page-level error.tsx boundary with its red error box and retry button.

Last updated on