Problem
During truly synchronous rapid A→B→C navigation (both clicks fired in the same JavaScript execution tick), vinext triggers a full page reload instead of completing a soft RSC navigation.
Evidence
E2E test rapid-navigation.spec.ts was modified in PR #745 to verify no full page reload occurs during rapid navigation:
// Set marker to detect full page reload
await page.evaluate(() => {
(window as any).__NAV_MARKER__ = "started-at-a";
});
// Click B then immediately click C (synchronously, same tick)
await page.evaluate(() => {
const linkB = document.querySelector('[data-testid="page-a-link-to-b"]') as HTMLElement;
const linkC = document.querySelector('[data-testid="page-a-link-to-c"]') as HTMLElement;
if (linkB) linkB.click();
if (linkC) linkC.click();
});
// Verify no full page reload happened (marker should survive)
const marker = await page.evaluate(() => (window as any).__NAV_MARKER__);
expect(marker).toBe("started-at-a"); // ❌ FAILS: marker is undefined
Result: __NAV_MARKER__ is undefined, indicating a full page reload wiped all JavaScript state.
Key Observations
-
No error logged: No [vinext] RSC navigation error: appears in console, ruling out the catch block at app-browser-entry.ts:809 as the source.
-
Works with sequential clicks: If the two clicks have even a tiny delay (sequential page.click() calls), navigation completes correctly.
-
Same-route navigation passes: The same-route query change during cross-route navigation test passes with the same synchronous click pattern.
Difference Between Passing and Failing
| Test |
isSameRoute |
Result |
| Same-route query change |
true |
✅ Passes |
| Cross-route A→B→C |
false |
❌ Full page reload |
This suggests the issue is related to cross-route navigation behavior when isSameRoute = false.
Root Cause Hypothesis
When isSameRoute = false, the navigation uses useTransition = false in renderNavigationPayload(). Combined with truly synchronous clicks:
- Navigation B starts with
isSameRoute = false
- Navigation C starts in the same JavaScript tick before B can await anything
- State management (
activeNavigationId, pendingPathname, navigationSnapshotActiveCount) may have an edge case
- Something triggers
window.location.href = href without logging an error
Potential trigger points for hard navigation without error logging:
app-browser-entry.ts:728 — redirect handling (unlikely, no redirect involved)
app-browser-entry.ts:809 — catch block (but no error is logged)
- External URL detection in
navigateClientSide() (unlikely, same-origin URLs)
Investigation Needed
-
Add diagnostic logging: Temporarily add console.log before every window.location.href assignment to identify which code path triggers the reload.
-
Check state at navigation start: Log activeNavigationId, pendingPathname, navigationSnapshotActiveCount when each navigation starts.
-
Compare with Next.js behavior: Verify if Next.js handles synchronous rapid navigation differently.
Related
Workaround
The E2E test was updated to remove the marker check, allowing CI to pass while this is investigated. The test still covers rapid navigation but without verifying the no-reload invariant.
Files Affected
packages/vinext/src/server/app-browser-entry.ts — RSC navigation logic
packages/vinext/src/shims/link.tsx — Link click handler
packages/vinext/src/shims/navigation.ts — Client navigation state
Severity
High — Full page reload during what should be a smooth client-side navigation breaks the SPA experience and loses all client-side state.
Problem
During truly synchronous rapid A→B→C navigation (both clicks fired in the same JavaScript execution tick), vinext triggers a full page reload instead of completing a soft RSC navigation.
Evidence
E2E test
rapid-navigation.spec.tswas modified in PR #745 to verify no full page reload occurs during rapid navigation:Result:
__NAV_MARKER__isundefined, indicating a full page reload wiped all JavaScript state.Key Observations
No error logged: No
[vinext] RSC navigation error:appears in console, ruling out the catch block atapp-browser-entry.ts:809as the source.Works with sequential clicks: If the two clicks have even a tiny delay (sequential
page.click()calls), navigation completes correctly.Same-route navigation passes: The
same-route query change during cross-route navigationtest passes with the same synchronous click pattern.Difference Between Passing and Failing
isSameRoutetruefalseThis suggests the issue is related to cross-route navigation behavior when
isSameRoute = false.Root Cause Hypothesis
When
isSameRoute = false, the navigation usesuseTransition = falseinrenderNavigationPayload(). Combined with truly synchronous clicks:isSameRoute = falseactiveNavigationId,pendingPathname,navigationSnapshotActiveCount) may have an edge casewindow.location.href = hrefwithout logging an errorPotential trigger points for hard navigation without error logging:
app-browser-entry.ts:728— redirect handling (unlikely, no redirect involved)app-browser-entry.ts:809— catch block (but no error is logged)navigateClientSide()(unlikely, same-origin URLs)Investigation Needed
Add diagnostic logging: Temporarily add
console.logbefore everywindow.location.hrefassignment to identify which code path triggers the reload.Check state at navigation start: Log
activeNavigationId,pendingPathname,navigationSnapshotActiveCountwhen each navigation starts.Compare with Next.js behavior: Verify if Next.js handles synchronous rapid navigation differently.
Related
isSameRouteclassification (broader issue, fixed by PR fix(navigation): track pending pathname to fix isSameRoute race condition during rapid navigation #745)pendingPathnametracking to fixisSameRouteraceWorkaround
The E2E test was updated to remove the marker check, allowing CI to pass while this is investigated. The test still covers rapid navigation but without verifying the no-reload invariant.
Files Affected
packages/vinext/src/server/app-browser-entry.ts— RSC navigation logicpackages/vinext/src/shims/link.tsx— Link click handlerpackages/vinext/src/shims/navigation.ts— Client navigation stateSeverity
High — Full page reload during what should be a smooth client-side navigation breaks the SPA experience and loses all client-side state.