Skip to content

feat(router): expose opt-in navigation diagnostics#2598

Open
NathanDrake2406 wants to merge 7 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/debug-navigation-traces
Open

feat(router): expose opt-in navigation diagnostics#2598
NathanDrake2406 wants to merge 7 commits into
cloudflare:mainfrom
NathanDrake2406:nathan/debug-navigation-traces

Conversation

@NathanDrake2406

@NathanDrake2406 NathanDrake2406 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Overview

Area Change
Configuration Adds the disabled-by-default VINEXT_DEBUG_NAVIGATION=1 build-time flag
Runtime Emits correlated reuse, prefetch, optimistic-shell, fetch, abort, synchronous history-restore, and commit events
Architecture Keeps browser entry points wiring-only and places deterministic event construction and navigation correlation in a separate client module
Safety Includes application URLs for correlation, but never response bodies, cookies, or request headers
Coverage Exercises fresh dynamic-param navigation and committed-cache sibling-route Back restoration in a self-contained Playwright project

Why

Issue #2597 reports a navigation race whose final DOM mixes stale server-layout data with current client params. The reporter needs the existing structured NavigationTrace decisions at runtime together with the concrete RSC URL, cache key, fetch lifecycle, supersession, history-snapshot restoration, and final commit outcome.

Running the PR preview against the reporter's public reproduction now captures the failure: the B RSC request succeeds and reaches an authoritative commit, but that commit preserves the dynamic layout identity carried through the detached optimistic shell. The diagnostics therefore narrow the follow-up fix to the optimistic-to-authoritative preservation boundary rather than history snapshot restoration.

The original diagnostic path entered through navigateRsc(). Synchronous history-snapshot restores bypass that function, even though they can commit a cached route tree directly. Leaving that boundary unreported made the diagnostics incomplete for the issue's sibling-route Back symptom.

This PR adds observability without changing navigation decisions. Refs #2597.

What changed

Scenario Before After
Fresh or prefetched navigation Correlated reuse, fetch, abort, and commit events Unchanged
Synchronous history-snapshot restore Commit event had no correlated start lifecycle Emits a correlated traverse start followed by a committed-cache commit
Dynamic param A → Back → B No issue-shaped diagnostic regression Verifies B issues an RSC request and commits matching server/client params
Sibling child → Back Restore path was not covered by diagnostics E2E Verifies server page, client params, and usePathname() restore together
Diagnostic E2E bootstrap Relied on inherited process state and could reuse a non-diagnostic optimized graph locally Owns the build-time flag in a dedicated Vite config and forces optimizer isolation
Pages Router instrumentation tests Request-error cleanup could erase the one-time startup signal in a parallel test Clears only request errors and preserves the startup invariant

The diagnostic schema remains internal and may change between releases.

Validation
Risk and compatibility
  • No public API change.
  • Diagnostics remain disabled by default.
  • Navigation, history restoration, and cache decisions are unchanged.
  • The restore lifecycle is started only after a snapshot is approved, so cache misses still fall through to the existing navigateRsc() lifecycle without a phantom diagnostic event.
  • The reporter is settled in finally so failed restore attempts cannot leave stale diagnostic correlation state.
Non-goals

References

Reference Why it matters
Issue #2597 Reports stale dynamic-layout data and mixed sibling-route history restoration
Prior test-only debugger #1168 Earlier invariant debugger that did not expose browser lifecycle telemetry
Existing vinext navigation trace model Structured planner and commit decision model reused by this PR
Next.js navigation debug-info boundary Upstream precedent for retaining navigation debug context

Refs #2597

App Router navigation decisions already produce structured traces, but applications cannot observe them at runtime. This makes cache, prefetch, optimistic-shell, fetch, supersession, and commit races dependent on patched bundles.

Add a disabled-by-default VINEXT_DEBUG_NAVIGATION flag, a deterministic reporter, thin lifecycle wiring, documentation, and behavior tests. Events include application URLs but never response bodies, cookies, or request headers.
@pkg-pr-new

pkg-pr-new Bot commented Jul 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2598
npm i https://pkg.pr.new/create-vinext-app@2598
npm i https://pkg.pr.new/vinext@2598

commit: 078d53a

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 078d53a against base 8d0a18d using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 126.6 KB 127.2 KB ⚫ +0.5%
Client entry size (gzip) vinext 120.6 KB 121.2 KB ⚫ +0.5%
Dev server cold start vinext 2.70 s 2.73 s ⚫ +1.2%
Production build time vinext 2.86 s 2.83 s ⚫ -1.1%
RSC entry closure size (gzip) vinext 98.6 KB 98.6 KB ⚫ -0.0%
Server bundle size (gzip) vinext 165.3 KB 165.3 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

Navigation diagnostics are intentionally an operational debugging stream, but the documentation did not state whether their event shape was stable. Consumers could otherwise mistake field and phase names for a supported public API.

Document that the schema is internal and may change between releases.
The diagnostic reporter and commit callbacks were tested separately, so a wiring regression could break cross-boundary navigation correlation without failing the suite.

Exercise a real App Router refresh from start through fetch and commit, and derive diagnostic navigation and reuse kinds from the planner-owned unions to prevent type drift.
@NathanDrake2406
NathanDrake2406 marked this pull request as draft July 12, 2026 07:33
Navigation diagnostics flattened planner and commit decisions into parallel fields, losing decision-specific data such as the fresh-fetch reason and obscuring which layer owned each result.

Pass reuse facts, reuse decisions, and commit approval decisions through unchanged. Keep the later visible runtime outcome separate because approval does not guarantee that a queued commit becomes visible.
The shared App Router Playwright server enabled navigation diagnostics for every browser test, removing default-disabled coverage and relying on POSIX-only inline environment syntax.

Move the lifecycle assertion to a dedicated Playwright project and server, pass the flag through Playwright environment configuration, and run the project independently in CI.
@NathanDrake2406
NathanDrake2406 marked this pull request as ready for review July 12, 2026 09:26
Synchronous App Router history restores commit cached snapshots without entering navigateRsc, so the opt-in diagnostics omit the lifecycle most relevant to mixed-tree traversal reports.

Start and settle a correlated traverse lifecycle at the approved snapshot-restore boundary. Cover both the reported dynamic-param sequence and sibling-route Back restoration in the dedicated diagnostics project.
Pages Router request-error tests cleared the one-time register marker, racing the startup assertion in the shared dev server. The navigation diagnostics project could also reuse an optimized app-basic graph compiled without diagnostics during local runs.

Preserve the startup marker while clearing request errors, and give the diagnostics project an owned Vite config with forced optimization. The Playwright regressions now exercise both boundaries deterministically.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App Router: soft navigation between different dynamic params reuses the committed shared-layout subtree without fetching the new RSC payload

1 participant