Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nextjs
SDK Version
10.72.0 (measured); the import chain is unchanged in 10.73.0 and on develop
Framework Version
Next 16.3.3, App Router only (no pages/ directory), Turbopack
Link to Sentry event
No response
Reproduction Example/SDK Setup
// instrumentation-client.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN });
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
Nothing else is needed: any App Router app that calls init() reproduces it. The app has no pages/ directory and no import of next/router anywhere.
Steps to Reproduce
npx create-next-app@latest (App Router), add @sentry/nextjs and the instrumentation-client.ts above.
next build --experimental-analyze (or any other bundle analysis).
- Open the chunk that contains the SDK client (the one with the
[@sentry/nextjs] init warnings).
Expected Result
An App Router app downloads no Pages Router code. The SDK already decides at runtime which router it is on (routing/nextRoutingInstrumentation.ts: !WINDOW.document.getElementById('__NEXT_DATA__')), so the Pages Router branch is never taken on App Router.
Actual Result
The SDK client chunk carries the whole Pages Router client runtime: next/dist/shared/lib/router/router.js, next/dist/compiled/path-to-regexp, next/dist/client/route-loader.js, next/dist/client/script.js, sorted-routes.js, route-regex.js, prepare-destination.js, ... 52 modules from next, 87.2 KB raw / 35.6 KB gzip in our app (Turbopack analyzer, Next 16.3.3).
Sentry client chunk (App Router, __SENTRY_TRACING__ compiled to false) |
raw |
of which next (Pages Router runtime) |
@sentry/nextjs 10.72.0 as published |
168.4 KB |
87.2 KB (52 modules) |
with next/router imported on demand (see below) |
82 KB |
0 - the runtime lands in an async chunk that an App Router app never requests |
It is there with tracing enabled and after compiling tracing out: the import is static, so no bundler can drop it.
Root cause - packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts:
import RouterImport from 'next/router';
const Router: typeof RouterImport = RouterImport.events
? RouterImport
: (RouterImport as unknown as { default: typeof RouterImport }).default;
reached statically through client/index.ts -> browserTracingIntegration.ts -> routing/nextRoutingInstrumentation.ts -> routing/pagesRouterRoutingInstrumentation.ts. Router is used in exactly one place, pagesRouterInstrumentNavigation (Router.events.on('routeChangeStart', ...)); the pageload half only reads __NEXT_DATA__ and __BUILD_MANIFEST. next declares no sideEffects, and the app/pages decision is made at runtime, so the module and everything next/router pulls in stay in every client bundle.
Why the SDK's own size gate cannot see it: .size-limit.js measures @sentry/nextjs (client) with ignore: ['next/router', 'next/constants'].
Additional Context
What I measured before proposing a fix (same app, same analyzer, patching the installed build):
- Moving the module-scope interop (
RouterImport.events ? ...) into a function changes nothing: the cost is the static import at module-graph level, not the getter access.
- Replacing the static import with
import('next/router') inside pagesRouterInstrumentNavigation moves the Pages Router runtime into an async chunk: Sentry chunk 168.4 -> 82 KB raw on our App Router app. On a Pages Router app the routeChangeStart listener is registered once that import settles; the router module is already part of the framework runtime there. A minimal Pages Router app built with both Turbopack and webpack, pageload and mount-time router.replace() navigation spans included, is in the PR that follows.
window.next.router.events (no next/router at all) was considered and rejected: Next marks instance.events as "never documented ... remove the following major version".
Priority
React with 👍 to help prioritize this issue.
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nextjs
SDK Version
10.72.0 (measured); the import chain is unchanged in 10.73.0 and on
developFramework Version
Next 16.3.3, App Router only (no
pages/directory), TurbopackLink to Sentry event
No response
Reproduction Example/SDK Setup
Nothing else is needed: any App Router app that calls
init()reproduces it. The app has nopages/directory and no import ofnext/routeranywhere.Steps to Reproduce
npx create-next-app@latest(App Router), add@sentry/nextjsand theinstrumentation-client.tsabove.next build --experimental-analyze(or any other bundle analysis).[@sentry/nextjs]init warnings).Expected Result
An App Router app downloads no Pages Router code. The SDK already decides at runtime which router it is on (
routing/nextRoutingInstrumentation.ts:!WINDOW.document.getElementById('__NEXT_DATA__')), so the Pages Router branch is never taken on App Router.Actual Result
The SDK client chunk carries the whole Pages Router client runtime:
next/dist/shared/lib/router/router.js,next/dist/compiled/path-to-regexp,next/dist/client/route-loader.js,next/dist/client/script.js,sorted-routes.js,route-regex.js,prepare-destination.js, ... 52 modules fromnext, 87.2 KB raw / 35.6 KB gzip in our app (Turbopack analyzer, Next 16.3.3).__SENTRY_TRACING__compiled tofalse)next(Pages Router runtime)@sentry/nextjs10.72.0 as publishednext/routerimported on demand (see below)It is there with tracing enabled and after compiling tracing out: the import is static, so no bundler can drop it.
Root cause -
packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts:reached statically through
client/index.ts->browserTracingIntegration.ts->routing/nextRoutingInstrumentation.ts->routing/pagesRouterRoutingInstrumentation.ts.Routeris used in exactly one place,pagesRouterInstrumentNavigation(Router.events.on('routeChangeStart', ...)); the pageload half only reads__NEXT_DATA__and__BUILD_MANIFEST.nextdeclares nosideEffects, and the app/pages decision is made at runtime, so the module and everythingnext/routerpulls in stay in every client bundle.Why the SDK's own size gate cannot see it:
.size-limit.jsmeasures@sentry/nextjs (client)withignore: ['next/router', 'next/constants'].Additional Context
What I measured before proposing a fix (same app, same analyzer, patching the installed build):
RouterImport.events ? ...) into a function changes nothing: the cost is the static import at module-graph level, not the getter access.import('next/router')insidepagesRouterInstrumentNavigationmoves the Pages Router runtime into an async chunk: Sentry chunk 168.4 -> 82 KB raw on our App Router app. On a Pages Router app therouteChangeStartlistener is registered once that import settles; the router module is already part of the framework runtime there. A minimal Pages Router app built with both Turbopack and webpack, pageload and mount-timerouter.replace()navigation spans included, is in the PR that follows.window.next.router.events(nonext/routerat all) was considered and rejected: Next marksinstance.eventsas "never documented ... remove the following major version".Priority
React with 👍 to help prioritize this issue.