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.
