mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[GH-ISSUE #744] Race condition in isSameRoute classification causes janky transitions during rapid navigation #165
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#165
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @Divkix on GitHub (Apr 1, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/744
Problem
During rapid successive navigations (e.g., user clicks A→B→C quickly), the
isSameRoutecheck inapp-browser-entry.tscompares against stalewindow.location.pathnamebecause URL commits are deferred viauseLayoutEffect.Current Behavior (lines 619-622)
Race condition window:
Navigation 1 starts (A→B):
renderNavigationPayload()called withisSameRoute=false(correctly: B ≠ A)navigationCommitEffectqueues URL update viapushHistoryStateWithoutNotify()window.locationNavigation 2 starts (B→C) before Navigation 1 commits:
isSameRoutecomparesurl.pathname(C) withwindow.location.pathname(still A)Navigation 1 eventually commits:
commitClientNavigationState()callssyncCommittedUrlStateFromLocation()state.cachedPathnameupdates to BisSameRoutewas already evaluated incorrectlyImpact
startTransitionstartTransition(rare but possible)User-facing: Janky, non-smooth transitions when rapidly clicking between routes or changing filters during page loads.
Code Reference
This was acknowledged as a known limitation in PR #690 with this comment (lines 621-625):
This issue tracks implementing a proper fix rather than accepting the workaround.
Root Cause
The
ClientNavigationStatetracks pending params but not pending pathname:Proposed Solution
Option A: Track Pending Pathname (Recommended)
Add
pendingPathnametoClientNavigationStateinnavigation.ts:Flow changes:
pendingPathnameimmediately when__VINEXT_RSC_NAVIGATE__startsisSameRoutecomparison:pendingPathnameincommitClientNavigationState()after successful commitComplexity: Requires restructuring navigation flow because
isSameRouteis evaluated beforestageClientParams()in both cached and non-cached paths.Option B: Track In-Flight Navigation Destination
Add a
destinationPathnamefield set immediately when navigation starts, before any async work. This separates "where we are going" from "params we are staging".Edge Cases to Handle
Overlapping navigations (A→B→C): When C starts, should compare against B (most recent pending), not A. The
navId/activeNavigationIdsystem already supersedes older navigations.Navigation superseded before commit: If B→C starts but is superseded by B→D, pending pathname should update to D. Cleanup must be robust.
Error during navigation: If navigation fails,
pendingPathnamemust be cleared in catch blocks. Current_snapshotPendingpattern shows the complexity of cleanup.Back/forward (traverse) navigation: Different code path via
popstatehandler. Useswindow.location.hrefdirectly, less affected.Files Affected
packages/vinext/src/shims/navigation.ts— addpendingPathnameto state type, updatecommitClientNavigationState()packages/vinext/src/server/app-browser-entry.ts— updateisSameRoutelogic, set/clear pending pathnameWhy This Matters Now
UX regression: Rapid navigation is a common user pattern. The current workaround produces visible jank.
Self-contained fix: ~30 lines changed, does not depend on major refactors like #726 (layout persistence architecture).
Code health: The comment acknowledges this as debt. Better to fix properly than let it linger.
Related
Testing Needed
isSameRouteclassification with simulated pending navigation