Next.js Error Handling

Errors happen in every application. A database call fails, an API times out, a user visits a page that does not exist. Next.js gives you dedicated files to handle each type of error so that one broken part of your app does not break the entire experience.

The error.js File

Create a file named error.js inside any route folder. When an error occurs inside a page in that folder, Next.js renders this file instead of crashing the whole page. The rest of your app keeps working normally.

'use client';  // error.js must be a Client Component

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

The error prop contains error details. The reset function attempts to re-render the page. Clicking "Try again" re-runs the component that failed.

Diagram: How error.js Catches Errors

NORMAL FLOW:
Layout → Page renders → User sees page

ERROR FLOW (without error.js):
Layout → Page throws error → Entire app crashes

ERROR FLOW (with error.js):
Layout → Page throws error → error.js renders instead
         ↑ Layout stays intact, user sees error UI not a crash

Error Boundary Coverage

An error.js file catches errors from its own page and any child components. It does not catch errors from the layout in the same folder. To catch layout errors, place an error.js one level up.

app/
  error.js            ← Catches errors from the root layout's children
  layout.js
  page.js
  dashboard/
    error.js          ← Catches errors from dashboard pages only
    layout.js
    page.js
    analytics/
      page.js         ← If this throws, dashboard/error.js catches it

The not-found.js File

When a user visits a URL that does not match any page, Next.js shows a 404 page. Create not-found.js in the app/ folder to customize this page.

// app/not-found.js
export default function NotFound() {
  return (
    <main>
      <h1>404 – Page Not Found</h1>
      <p>The page you are looking for does not exist.</p>
      <a href="/">Go back home</a>
    </main>
  );
}

Triggering the Not Found Page Manually

Inside a page or server component, call notFound() from next/navigation to trigger the 404 page when data is missing.

import { notFound } from 'next/navigation';

export default async function BlogPost({ params }) {
  const post = await fetchPost(params.slug);

  if (!post) {
    notFound();   // Renders not-found.js for this route segment
  }

  return <article>{post.title}</article>;
}

The global-error.js File

For errors that happen inside the root layout itself — which error.js cannot catch — create a global-error.js file in the app/ folder. This file replaces the entire page, including the layout, so it must include its own <html> and <body> tags.

// app/global-error.js
'use client';

export default function GlobalError({ error, reset }) {
  return (
    <html>
      <body>
        <h1>A critical error occurred</h1>
        <p>{error.message}</p>
        <button onClick={reset}>Try again</button>
      </body>
    </html>
  );
}

Handling Errors in Server Actions

When a server action fails, wrap the logic in a try-catch block and return an error state to the component.

// app/actions.js
'use server';

export async function createUser(formData) {
  try {
    const name = formData.get('name');
    await db.users.create({ name });
    return { success: true };
  } catch (error) {
    return { success: false, message: 'Failed to create user' };
  }
}

Diagram: Which File Handles Which Error

SCENARIO                          FILE THAT HANDLES IT
─────────────────────────────────────────────────────
Page throws an error              error.js (same folder)
URL does not match any route      not-found.js
notFound() called in code         not-found.js (that segment)
Root layout crashes               global-error.js
API route fails                   Handled inside route.js itself

Logging Errors

The error prop in error.js gives you the error object. Send it to an error tracking service like Sentry to monitor and fix issues in production.

'use client';
import { useEffect } from 'react';

export default function Error({ error, reset }) {
  useEffect(() => {
    // Send to your error monitoring service
    console.error('Page error:', error);
    // logErrorToService(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Key Takeaway

Use error.js to catch runtime errors within a route segment and show a helpful message instead of crashing. Use not-found.js for missing pages and call notFound() when data is absent. Use global-error.js as a last resort for root layout failures. Always mark error files with 'use client' since they need the reset function to work.

Leave a Comment