GetLaunchpad
Back to blog
5 min read

Setting up Sentry error tracking in Next.js (server and client)

Shipping without error tracking means finding out about crashes from angry users. Here's how to install @sentry/nextjs, capture server-side exceptions in API routes, add a client error boundary, upload source maps, and filter out the noise.

Shipping to production without error tracking is flying blind. You find out about crashes from angry users or, worse, you never find out at all. Sentry fixes this — it captures unhandled exceptions, surfaces the exact stack trace, and tells you how many users were affected. Here's how to wire it up correctly in a Next.js App Router project, covering both server and client errors.

Install the SDK

npm install @sentry/nextjs

Sentry provides an init wizard, but it is easier to configure manually so you know exactly what each file does. You need three config files and a small change to next.config.js.

Add your DSN to environment variables

Create a project in your Sentry dashboard and grab the DSN from Settings → Projects → Client Keys. Add it to .env.local:

NEXT_PUBLIC_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SENTRY_AUTH_TOKEN=sntrys_...   # for uploading source maps
SENTRY_ORG=your-org-slug
SENTRY_PROJECT=your-project-slug

The DSN is safe to expose publicly — it only lets Sentry receive events, not read them. The auth token is server-only and is only used during the build step.

Client-side config

Create sentry.client.config.ts at the project root:

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

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,      // capture 20% of transactions for performance
  replaysSessionSampleRate: 0, // disable session replays (saves quota)
  replaysOnErrorSampleRate: 1, // but always capture a replay on error
  integrations: [
    Sentry.replayIntegration(),
  ],
});

Server-side config

Create sentry.server.config.ts at the project root:

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

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,
});

Create sentry.edge.config.ts for middleware and edge routes:

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

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,
});

Wrap next.config.js with withSentryConfig

// next.config.js
const { withSentryConfig } = require("@sentry/nextjs");

const nextConfig = {
  // your existing config
};

module.exports = withSentryConfig(nextConfig, {
  org: process.env.SENTRY_ORG,
  project: process.env.SENTRY_PROJECT,
  authToken: process.env.SENTRY_AUTH_TOKEN,
  silent: true,           // suppress build output noise
  hideSourceMaps: true,   // upload maps but don't serve them publicly
  disableLogger: true,
});

hideSourceMaps is important. Without it, anyone who views your production bundle can read your original source code.

Capturing server-side errors in API routes

Sentry automatically captures unhandled exceptions. For errors inside try/catch blocks — which you should have on every route handler — call Sentry.captureException explicitly:

// app/api/subscriptions/route.ts
import * as Sentry from "@sentry/nextjs";
import { auth } from "@clerk/nextjs/server";
import { adminClient } from "@/lib/supabase/admin";

export async function GET() {
  try {
    const { userId } = await auth();
    if (!userId) return new Response("Unauthorized", { status: 401 });

    const { data, error } = await adminClient
      .from("subscriptions")
      .select("*")
      .eq("user_id", userId)
      .single();

    if (error) throw error;
    return Response.json(data);
  } catch (err) {
    Sentry.captureException(err);
    return new Response("Internal Server Error", { status: 500 });
  }
}

The key habit: always call captureException before returning a 500. If you only rethrow, Next.js will catch it and may swallow the context Sentry needs to group errors correctly.

Client-side error boundary

React errors in client components are handled by an error boundary. Create app/error.tsx:

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

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

  return (
    <div className="flex min-h-screen flex-col items-center justify-center gap-4">
      <h2 className="text-xl font-semibold">Something went wrong</h2>
      <button
        onClick={reset}
        className="rounded-md bg-zinc-900 px-4 py-2 text-sm text-white hover:bg-zinc-700"
      >
        Try again
      </button>
    </div>
  );
}

Filtering noise: ignore 404s and expected errors

Not every error deserves a Sentry alert. 404s from bots, cancelled fetch requests, and expected auth failures will flood your inbox otherwise. Add a beforeSend hook to your server config:

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,
  beforeSend(event, hint) {
    const err = hint.originalException;

    // Drop not-found and unauthorized errors
    if (err instanceof Error) {
      if (err.message.includes("NEXT_NOT_FOUND")) return null;
      if (err.message.includes("Unauthorized")) return null;
    }

    // Drop 404 responses
    if (event.request?.url) {
      const status = (hint.originalException as Response)?.status;
      if (status === 404) return null;
    }

    return event;
  },
});

Verify it works

Add a temporary route that throws deliberately, hit it, and check your Sentry dashboard:

// app/api/sentry-test/route.ts — DELETE THIS AFTER TESTING
export async function GET() {
  throw new Error("Sentry test error");
}

You should see the error appear in Sentry within a few seconds with the full stack trace, the request URL, and the environment tag. Once confirmed, delete the test route.


Error tracking with Sentry, alongside structured logging and Stripe webhook handling, is pre-wired in GetLaunchpad, a Next.js 16 SaaS boilerplate. Ship with confidence from day one.

Share this article:Share on X

Ready to ship faster?

GetLaunchpad gives you everything covered in this guide — pre-configured, tested, and production-ready. Skip the setup and focus on your product.

Get the boilerplate →

More articles