Capturing Errors

Learn how to capture and report errors from your Next.js application to Sentry.

Sentry's Next.js SDK automatically captures most unhandled errors. However, Next.js has built-in error handling patterns that intercept errors before they reach Sentry. This guide covers when and why you need manual capture.

The SDK captures these without any code:

  • Unhandled exceptions in client-side code
  • Unhandled promise rejections
  • Server errors that crash (API routes, Server Components)

Next.js intercepts errors in these patterns, hiding them from Sentry:

  • Error boundaries (error.tsx) — catch rendering errors for UI recovery
  • Caught errors — any try/catch where you handle the error gracefully

If you catch an error and don't re-throw it, Sentry never sees it.

Copied
// Automatic: unhandled errors bubble up to Sentry
throw new Error("This is captured automatically");

// Manual needed: you caught it, Sentry doesn't see it
try {
  await riskyOperation();
} catch (error) {
  // Error is swallowed - add captureException
  Sentry.captureException(error);
  return { error: "Something went wrong" };
}

Next.js error.tsx files catch rendering errors to show a fallback UI. This is good for UX but means errors never reach Sentry's global handler.

Add captureException in every error boundary to maintain visibility.

app/error.tsx
Copied
"use client";

import { useEffect } from "react";
import * as Sentry from "@sentry/nextjs";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);

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

global-error.tsx is a last-resort safety net that only triggers when your root layout itself fails. This is rare—most errors are caught by route-level error.tsx files first.

Include <html> and <body> tags since it replaces the entire document.

app/global-error.tsx
Copied
"use client";

import { useEffect } from "react";
import * as Sentry from "@sentry/nextjs";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);

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

When you catch errors to return graceful responses (in Server Actions, API routes, or middleware), add captureException before returning.

The pattern is the same everywhere: if you catch and don't throw, call captureException.

app/actions.ts
Copied
"use server";

import * as Sentry from "@sentry/nextjs";

export async function createPost(formData: FormData) {
  try {
    const post = await db.posts.create({
      data: { title: formData.get("title") as string },
    });
    return { success: true, id: post.id };
  } catch (error) {
    Sentry.captureException(error);
    return { success: false, error: "Failed to create post" };
  }
}

Add metadata to help debug errors:

  • Tags: Searchable in Sentry's UI for filtering and grouping (e.g., by feature area or user tier)
  • Extra: Displayed in event details for debugging values like IDs (not searchable)
Copied
Sentry.captureException(error, {
  tags: { section: "checkout" },
  extra: { orderId, userId: user.id },
});

See Enriching Events for more options like breadcrumbs, user context, and attachments.

For automatic tracing in Server Actions, see withServerActionInstrumentation.

ScenarioCaptured Automatically?Action Needed
Unhandled client errorYesNone
Unhandled server crashYesNone
error.tsx boundaryNoAdd captureException
try/catch with graceful returnNoAdd captureException
try/catch that re-throwsYesNone

Error boundary placement:

Copied
app/
├── global-error.tsx      # Root layout errors (rare)
├── error.tsx             # App-wide fallback
└── dashboard/
    └── error.tsx         # Dashboard-specific handling

  1. Error boundary intercepting — Check that captureException is called in your error.tsx files
  2. SDK not initialized — Verify config files exist with correct DSN
  3. beforeSend filtering — Check if a hook is dropping errors
app/error.tsx
Copied
"use client";

import { useEffect } from "react";
import * as Sentry from "@sentry/nextjs";

export default function Error({ error }: { error: Error }) {
  useEffect(() => {
    // This line is required!
    Sentry.captureException(error);
  }, [error]);

  return <div>Something went wrong</div>;
}

If you see the same error twice, you're likely capturing in multiple places. Check that only one handler captures each error.

Copied
// Don't capture in both error.tsx AND a parent component
// Pick one location per error

Server errors may show minified traces in production. Enable source maps for readable traces.

The digest property on server errors is a hash you can search for in logs.

Copied
// Server errors include a digest for debugging
error: Error & { digest?: string }

// Log it for cross-referencing
console.error("Error digest:", error.digest);

See Source Maps for setup instructions.

Was this helpful?
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").