mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #643] [CLOSED] feat: two-phase navigation commit with visited response cache #740
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#740
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?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/643
Author: @NathanDrake2406
Created: 3/22/2026
Status: ❌ Closed
Base:
main← Head:fix/app-router-navigation-cache-replay📝 Commits (10+)
a83634dFix app router navigation replay and refresh caching5729d0ffix: address review issues in navigation cache/replay3303a3ftest: add navigation regression e2e tests and fixtures4f313fcfix: update form tests for navigateClientSide calling convention85df5b7fix: revert behavioral changes to match Codex's original intentc864945fix: buffer RSC responses, fix shallow routing, server action redirects, and Firefox nav hang4b57ce2refactor: remove animation suppression — it needs an architectural fix046087crefactor: replace useEffect with useLayoutEffect in NavigationCommitSignal072087efix: skip startTransition for cross-route navigations (Firefox hang)3afababfix: address code review findings from elegance pass📊 Changes
19 files changed (+2040 additions, -304 deletions)
View changed files
📝
packages/vinext/src/global.d.ts(+12 -4)📝
packages/vinext/src/server/app-browser-entry.ts(+453 -48)📝
packages/vinext/src/shims/form.tsx(+5 -8)📝
packages/vinext/src/shims/link.tsx(+19 -97)📝
packages/vinext/src/shims/navigation.ts(+484 -123)➕
tests/e2e/app-router/navigation-regressions.spec.ts(+686 -0)📝
tests/e2e/app-router/nextjs-compat/actions-revalidate.spec.ts(+36 -1)➕
tests/fixtures/app-basic/app/nav-flash/link-sync/FilterLinks.tsx(+36 -0)➕
tests/fixtures/app-basic/app/nav-flash/link-sync/page.tsx(+56 -0)➕
tests/fixtures/app-basic/app/nav-flash/list/page.tsx(+30 -0)➕
tests/fixtures/app-basic/app/nav-flash/param-sync/[filter]/FilterControls.tsx(+28 -0)➕
tests/fixtures/app-basic/app/nav-flash/param-sync/[filter]/page.tsx(+39 -0)➕
tests/fixtures/app-basic/app/nav-flash/provider/[id]/page.tsx(+15 -0)➕
tests/fixtures/app-basic/app/nav-flash/query-sync/FilterControls.tsx(+28 -0)➕
tests/fixtures/app-basic/app/nav-flash/query-sync/page.tsx(+56 -0)📝
tests/fixtures/app-basic/app/nextjs-compat/action-revalidate/page.tsx(+4 -0)📝
tests/fixtures/app-basic/app/page.tsx(+9 -0)📝
tests/form.test.ts(+23 -14)📝
tests/prefetch-cache.test.ts(+21 -9)📄 Description
Ref #639
Summary
Redesigns the App Router client-side navigation system to eliminate URL/hook desync flashes and enable instant back/forward navigation.
Core architecture changes
useLayoutEffectafter React commits the new tree, preventingusePathname()/useSearchParams()/useParams()from returning stale values during transitionsClientNavigationRenderSnapshot) provides the pending URL and params to hooks during the render phase, before history is committed. This means components see consistent navigation state throughout the entire render cyclesnapshotRscResponsebefore passing tocreateFromFetch, ensuring the flight parser processes all rows in a single synchronous pass (prevents partial tree commits where list content updates before heading hooks catch up)ArrayBufferafter each navigation and cached (5 min TTL for forward nav, no expiry for back/forward, 50 entry cap). Back/forward navigations replay from cache instantly, matching Next.js BFCache behavior.router.refresh()bypasses the cache_navigationSnapshotActiveCount). After commit, hooks fall through touseSyncExternalStoreso userpushState/replaceStatecalls are immediately reflectedconsumePrefetchResponseonly returns settled responses synchronously, matching Next.js's segment cache behavior. Pending prefetches never block navigation (fixes Firefox nav bar hang)pushHistoryStateWithoutNotify/replaceHistoryStateWithoutNotifywrap the original (unpatched) history methods to update the URL without triggeringuseSyncExternalStoresubscribers prematurelyhistory.pushStatebefore fire-and-forget__VINEXT_RSC_NAVIGATE__with.catchcleanup, avoiding deadlock between the form's outeruseTransitionandrenderNavigationPayload's innerstartTransitionUnified navigation surface
navigateClientSide()is now the single entry point for all client-side navigation (Link, Form,router.push/replace)navigateClientSidewhich coordinates with__VINEXT_RSC_NAVIGATE__navigateClientSideNext.js parity notes
This brings vinext closer to Next.js for common navigation cases (forward nav, back/forward, prefetch reuse, shallow routing):
STATIC_STALETIME_MS). Back/forward bypasses TTL entirely (matches BFCache behavior where entries are served regardless of age). Eviction is LRU-only (50 entries)useLayoutEffectflow, vs Next.js's 2000+ line router reducer state machineArrayBufferis minimal compared to Next.js's multi-tier Flight cacheKnown limitation: CSS animation replay on navigation
vinext replaces the entire page tree on navigation, causing all CSS animations (fade-in, slide-in) to replay on freshly-mounted elements. Next.js avoids this architecturally via segment-level caching - only changed segments re-mount, so unchanged layouts/templates keep their DOM elements. This is a deeper architectural issue tracked separately from this PR.
Test plan
pnpm test tests/shims.test.ts tests/link.test.ts tests/app-router.test.ts- all pass (1074 tests)pnpm run test:e2e- all pass (501 tests, 0 failures)pnpm run check- zero lint, type, or formatting errorspushState/replaceStateupdates reflected inusePathname/useSearchParamsredirect()navigates correctly🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.