Skip to content

Next.js app router: router.back()/router.forward() navigations are tagged browser.popstate under span streaming #23909

Description

@chargome

What

With span streaming enabled, a router.back() or router.forward() navigation in the Next.js app router produces a navigation span with navigation.type: 'browser.popstate' instead of router.back / router.forward.

Under traceLifecycle: 'static' the same navigations are tagged router.back / router.forward.

Evidence

Observed in the nextjs-app-dir E2E app (Next 14.2.35, production build) while porting it to span streaming in #23905. The streamed event dump for a full run contains no router.back, router.forward or router.traverse value at all — only browser.popstate (6), router.push (7) and router.replace (2). The corresponding navigation spans:

navigation /navigation/:param/router-back   navigation.type=browser.popstate
navigation /navigation/:param/router-push   navigation.type=browser.popstate   (router.forward() case)

The transaction-based versions of these two tests pass on develop today, so this is specific to streaming.

Likely mechanism

In packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts, router-patch mode (which is what Next 14 uses, since it has no onRouterTransitionStart hook) starts a navigation span tagged router.back/router.forward with the placeholder name INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME, because back()/forward() carry no href.

The popstate listener is then supposed to rename that same span:

if (currentRouterPatchingNavigationSpanRef.current?.isRecording()) {
  currentRouterPatchingNavigationSpanRef.current.updateName(spanName);
  // ... does not touch navigation.type
} else {
  // creates a NEW span tagged 'browser.popstate'
}

Under streaming the ref appears to no longer be recording when popstate fires, so the else branch creates a second span tagged browser.popstate, and the original placeholder-named span is filtered out by the ignoreSpans entry that matches the placeholder name. The net effect is that the back/forward distinction is lost.

Impact

navigation.type no longer distinguishes back/forward from a plain popstate under streaming, for app router apps in router-patch mode.

Status

The two affected E2E tests (Creates a navigation span for \router.back()`and... `router.forward()``) are skipped in #23905 with a pointer to this issue, rather than having their expectations relaxed.

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions