Summary
@sentry/nextjs prepends basePath to the router.push / router.replace argument with an unguarded string concatenation. When the argument is an absolute URL, the two are glued together and the navigation span is named something like:
/hhttps://example.com/login
instead of /login. The navigation itself works correctly — only the span name is corrupted — so this shows up as junk entries in the transaction list and in dashboards, not as a user-facing failure.
Versions
@sentry/nextjs 10.22.0; also present in 11.0.0 (latest at time of writing)
next 15.5.18, App Router, basePath: '/h'
- Affects both navigation instrumentation modes (see below)
Root cause
build/cjs/client/routing/appRouterRoutingInstrumentation.js, in the transition-start-hook path:
const basePath = process.env._sentryBasePath ?? globalWithInjectedBasePath._sentryBasePath;
const normalizedHref = basePath && !href.startsWith(basePath) ? `${basePath}${href}` : href;
const unparameterizedPathname = new URL(normalizedHref, WINDOW.location.href).pathname;
With basePath = '/h' and href = 'https://example.com/login', href.startsWith('/h') is false, so the result is '/h' + 'https://example.com/login', and new URL(...).pathname faithfully returns /hhttps://example.com/login.
The same expression appears in the router-patch path, so both modes are affected.
Note the second-order effect: an href that already carries the base path — https://example.com/h/payments — is still concatenated, because as a string it starts with https, not /h.
Why Next.js itself is unaffected
Next's own addPathPrefix guards on the leading slash:
function addPathPrefix(path, prefix) {
if (!path.startsWith('/') || !prefix) {
return path;
}
...
}
So Next leaves absolute URLs alone, treats a same-origin absolute URL as an internal navigation, and routes correctly. Only the Sentry span name diverges from reality.
Reproduction
- Next.js App Router app with
basePath: '/h' and @sentry/nextjs client instrumentation.
- Call
router.push('https://<same-origin>/login') — or, more realistically, have a server component throw redirect('https://<same-origin>/login'). Next's RedirectBoundary catches it and calls router.push(url) internally, so this needs no unusual application code.
- Observe the resulting
navigation transaction name.
Expected: /login
Actual: /hhttps://<same-origin>/login
Absolute redirect targets are not exotic in a basePath app: Next's server redirect() runs the location through addPathPrefix, so an absolute URL is the documented way to send a user to a path outside the base path. Those same absolute URLs then reach the client router whenever the redirect is hit during a soft navigation.
We see seven distinct corrupted names in production across two apps over 90 days.
Suggested fix
Mirror Next's guard — only prefix root-relative paths:
-const normalizedHref = basePath && !href.startsWith(basePath) ? `${basePath}${href}` : href;
+const normalizedHref =
+ basePath && href.startsWith('/') && !href.startsWith(basePath) ? `${basePath}${href}` : href;
Both occurrences need it. The router-patch branch additionally guards typeof href === 'string' already, which the hook branch does not.
Investigated and written with Claude Code.
Summary
@sentry/nextjsprependsbasePathto therouter.push/router.replaceargument with an unguarded string concatenation. When the argument is an absolute URL, the two are glued together and the navigation span is named something like:instead of
/login. The navigation itself works correctly — only the span name is corrupted — so this shows up as junk entries in the transaction list and in dashboards, not as a user-facing failure.Versions
@sentry/nextjs10.22.0; also present in11.0.0(latest at time of writing)next15.5.18, App Router,basePath: '/h'Root cause
build/cjs/client/routing/appRouterRoutingInstrumentation.js, in thetransition-start-hookpath:With
basePath = '/h'andhref = 'https://example.com/login',href.startsWith('/h')isfalse, so the result is'/h' + 'https://example.com/login', andnew URL(...).pathnamefaithfully returns/hhttps://example.com/login.The same expression appears in the
router-patchpath, so both modes are affected.Note the second-order effect: an href that already carries the base path —
https://example.com/h/payments— is still concatenated, because as a string it starts withhttps, not/h.Why Next.js itself is unaffected
Next's own
addPathPrefixguards on the leading slash:So Next leaves absolute URLs alone, treats a same-origin absolute URL as an internal navigation, and routes correctly. Only the Sentry span name diverges from reality.
Reproduction
basePath: '/h'and@sentry/nextjsclient instrumentation.router.push('https://<same-origin>/login')— or, more realistically, have a server component throwredirect('https://<same-origin>/login'). Next'sRedirectBoundarycatches it and callsrouter.push(url)internally, so this needs no unusual application code.navigationtransaction name.Expected:
/loginActual:
/hhttps://<same-origin>/loginAbsolute redirect targets are not exotic in a
basePathapp: Next's serverredirect()runs the location throughaddPathPrefix, so an absolute URL is the documented way to send a user to a path outside the base path. Those same absolute URLs then reach the client router whenever the redirect is hit during a soft navigation.We see seven distinct corrupted names in production across two apps over 90 days.
Suggested fix
Mirror Next's guard — only prefix root-relative paths:
Both occurrences need it. The
router-patchbranch additionally guardstypeof href === 'string'already, which the hook branch does not.Investigated and written with Claude Code.