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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions