Skip to main content

Sentry Should Be Optional in Your Next.js App

A shield icon protecting a circuit board
Apr 10, 20261 min readSentry, Next.js, TypeScript

The Problem

Sentry's Next.js SDK initializes in three places: instrumentation-client.ts (browser), sentry.server.config.ts (Node), and sentry.edge.config.ts (edge runtime). Each calls Sentry.init() at module scope. When the DSN environment variable is missing, the SDK throws, polluting the console with errors on every request.

That is fine in production where the DSN is always set. In local development, on a fresh clone, or in CI jobs that do not need error monitoring, it makes the terminal unreadable.

One Boolean, Three Files

The fix is a single exported constant in the shared config:

// lib/sentry.config.ts
export const sentryEnabled = !!publicEnv.NEXT_PUBLIC_SENTRY_CONFIG_ID;

Each initialization file wraps its Sentry.init() call in the guard:

// sentry.server.config.ts
import { sentryEnabled, sharedSentryConfig } from '@/lib/sentry.config';
 
if (sentryEnabled) {
  Sentry.init({ ...sharedSentryConfig });
}

The edge config is identical. The client config follows the same pattern but has additional work inside the guard: deferred integration loading via requestIdleCallback to avoid blocking LCP.

The Router Transition Export

Next.js 16 expects instrumentation-client.ts to export onRouterTransitionStart for Sentry's router instrumentation. When Sentry is disabled, the export should be undefined so Next.js skips it:

export const onRouterTransitionStart = sentryEnabled
  ? Sentry.captureRouterTransitionStart
  : undefined;

This avoids a runtime error from calling Sentry.captureRouterTransitionStart when the SDK was never initialized.

The Takeaway

SDKs that initialize at module scope should always be guarded by the presence of their configuration. One boolean in a shared config file is cheaper than debugging why local dev is spewing errors, and it keeps the "works on a fresh clone" contract honest.