Skip to content

fix(navigation): align segment cache basic prefetch behavior - #2871

Open
james-elicx wants to merge 4 commits into
mainfrom
codex/fix-segment-cache-basic-current
Open

fix(navigation): align segment cache basic prefetch behavior#2871
james-elicx wants to merge 4 commits into
mainfrom
codex/fix-segment-cache-basic-current

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • schedule Cache Components Link prefetches through route-tree and segment phases with Next.js-compatible priority and cancellation
  • retain authoritative prefetched shells for optimistic navigation while suspending unresolved nested boundaries
  • preserve prefetched and same-route layouts across the detached-to-authoritative handoff with validated reuse proofs
  • align exact-same-hash navigation refresh behavior

Validation

  • NEXT_TEST_CONCURRENCY=1 targeted Next.js E2E: test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts — 10 passed, 1 skipped
  • NEXT_TEST_CONCURRENCY=1 targeted Next.js E2E: test/e2e/app-dir/segment-cache/prefetch-scheduling/prefetch-scheduling.test.ts — 4 passed, 1 skipped
  • focused vinext tests: 452 passed across app optimistic routing, Link scheduling/navigation, browser entry, client reuse manifests, page rendering, and early navigation intent
  • independent cumulative review: no actionable findings at patch fingerprint d58cb55a28a82c0dc504345a1abd7ad4fd65b4504b1f4ef48ec86682e85b5d4b
  • git diff --check

This remains draft until Big Bonk and exact-head CI are clean.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

…he-basic-current

# Conflicts:
#	packages/vinext/src/shims/link.tsx
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: f3f7083

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@james-elicx

Copy link
Copy Markdown
Member Author

Exact-head validation at fa1e85192 after resolving the main conflict:

  • NEXT_TEST_CONCURRENCY=1 test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts: 10 passed, 1 skipped
  • NEXT_TEST_CONCURRENCY=1 test/e2e/app-dir/segment-cache/prefetch-scheduling/prefetch-scheduling.test.ts: 4 passed, 1 skipped
  • merged focused vinext suite: 654/654 passed

The conflict resolution preserves main’s single learning-only request for encoded-delimiter / fully dynamic root paths while retaining the two-phase scheduler elsewhere.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@james-elicx

Copy link
Copy Markdown
Member Author

Exact-head validation at 01a43c78e after the scheduler hot-path fix:

  • NEXT_TEST_CONCURRENCY=1 Segment Cache basic: 10 passed, 1 skipped
  • NEXT_TEST_CONCURRENCY=1 prefetch scheduling: 4 passed, 1 skipped
  • scheduler unit suite: 19/19 passed, with the whole file reduced from ~259 ms locally to ~10 ms after removing ineligible O(n) scans
  • Link/prefetch focused suite: 287/287 passed

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared f3f7083 against base d558d62 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 2 regressed · 4 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 135.2 KB 139.2 KB 🔴 +3.0%
Client entry size (gzip) vinext 122.7 KB 126.4 KB 🔴 +3.0%
Dev server cold start vinext 2.92 s 2.95 s ⚫ +1.0%
Production build time vinext 3.06 s 3.08 s ⚫ +0.7%
RSC entry closure size (gzip) vinext 114.2 KB 114.4 KB ⚫ +0.2%
Server bundle size (gzip) vinext 193.0 KB 193.4 KB ⚫ +0.2%

View detailed results and traces

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

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Big Bonk review — PR #2871

Reviewed all 8 non-test source changes (plus the ~1000-line app-optimistic-routing.ts traversal via a focused sub-review) and cross-checked the scheduler and navigation-classification changes against Next.js canary source. Overall the change is high-quality and well-tested. No blocking bugs found. A few observations below, ranked by importance.

1. createOptimisticNativePromiseView: latent transform-skip for already-fulfilled promises (low, latent)

packages/vinext/src/server/app-optimistic-routing.ts (~L548–623, mirrored in the non-native branch ~L674–687)

When sourceStatus === "fulfilled" && !deferFulfilledValue, the view is initialized with hasTransformedResolution = true and transformedResolution = fulfilledValue. The .then callback then returns transformedResolution (the raw fulfilledValue) without ever calling transformResolved. This is safe today only because both current callers pass either an identity transform or deferFulfilledValue = true. It is an implicit "caller must pre-transform fulfilledValue" contract that nothing enforces — a future caller passing a fulfilled promise with a real transform and deferFulfilledValue = false would silently drop the transform. Consider a short comment documenting the contract (or an assertion) to prevent a future footgun.

2. Identity divergence across async optimistic boundaries (low)

OptimisticResolvedThenable/OptimisticResolvedLazyNode (~L350/L354) and the preserveThenable pending branch (~L1139) each start a fresh traversal.transformed WeakMap. A node reachable both from the synchronously-walked tree and from inside a resolved thenable/lazy subtree is therefore transformed into two distinct view objects. Not a crash and React tolerates it; only matters if referential identity is relied on downstream. Genuinely self-referential promise graphs can also drive unbounded async re-rendering (never a synchronous stack overflow — the explicit work stack + shared traversal on the two sync lazy re-entries at ~L784/L959 correctly break sync cycles). Both are pathological/edge and arguably acceptable.

3. isOptimisticReactNodeProp treats any Set as node-bearing (negligible)

app-optimistic-routing.ts entry instanceof Set returns true unconditionally while Array/Map inspect their contents. This over-eagerly opts a Set of pure non-nodes into a cloned transformed Set. Correctness-preserving (the main Set branch handles it) and defensible (peeking a Set iterator during classification is unsafe), but worth a one-line comment noting the intentional coarseness.

Things I checked that are correct (not issues)

  • Scheduler mostRecentIntentTask not reset on cancel() — matches Next.js exactly: cancelPrefetchTask does not clear mostRecentlyHoveredLink, and reschedulePrefetchTask uses the same task === mostRecentlyHoveredLink ? Intent : priority rule. Parity, not a bug.
  • cancelAll() deleting from unfinishedTasks mid-for...of — safe per the JS spec.
  • Frozen/sealed handling in the traversal — preventExtensions/freeze/seal are always applied last (deferred finish items), on the fresh target. Ordering correct in all sites.
  • Map/Set container copy — entries live in internal slots (not Reflect.ownKeys), copied separately via entries()/values(); no double-processing or missed entries.
  • verifySameRouteLayoutClientReuse — well-gated (exact artifact compatibility + variantCacheKey + payloadHash, keyed on pathname+routeId); artifactCompatibility is guaranteed non-undefined by the L411 guard; browser only emits these entries on exact-same-route navigation.
  • popstate prefetch-cancel relocation — moving the cancel out of notifyAppPopstateNavigationStart into the browser-entry popstate handler (cancelAppPrefetchesForTraversal, gated behind isSameAppRoutePopstateTarget) is a parity improvement and now also cancels scheduler tasks.
  • Navigation-epoch refactor — epoch now bumps only via cancelLinkPrefetchTasks (through cancelAppPrefetchesForDestination); every real RSC-fetching nav path pairs the destination cancel with the reset, and hash-only/external navs correctly skip it.
  • next.hash !== current.hash additions in navigation-planner.ts and url-utils.ts — deliberate "exact-same-hash refresh" alignment, ported from the Next.js segment-cache-basic test and covered by navigation-planner-early-intent.test.ts.
  • Early prefetchUrl returns coercing to "fulfilled" for the segment-cache phase — correct: a skipped route-tree fetch means data is present, so the scheduler should advance to the segment phase.

Nothing here blocks merge. Items 1–3 are optional hardening/comments.

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2871.

Summary

I reviewed all 8 non-test source changes plus the ~1000-line app-optimistic-routing.ts traversal, and cross-checked the scheduler and navigation-classification logic against Next.js canary source. No blocking bugs found — the change is high quality and well-tested.

Findings (all optional):

  1. Latent transform-skip in createOptimisticNativePromiseView — an already-fulfilled, non-deferred promise skips transformResolved and uses the raw fulfilledValue. Safe today because both callers pass identity/deferred, but the implicit "caller must pre-transform" contract is unenforced. Suggest a comment/assertion.
  2. Identity divergence across async optimistic boundaries (fresh traversal maps) — edge/pathological, not a crash.
  3. Coarse Set classification in isOptimisticReactNodeProp — negligible, correctness-preserving.

Things I verified as correct (parity, not bugs):

  • Scheduler mostRecentIntentTask not reset on cancel — matches Next.js exactly
  • cancelAll() deleting mid-for...of — spec-safe
  • Frozen-object ordering, Map/Set container copying — sound
  • verifySameRouteLayoutClientReuse gating and artifactCompatibility non-undefined guard
  • popstate prefetch-cancel relocation and navigation-epoch refactor — both parity improvements
  • next.hash !== current.hash additions — deliberate, ported from Next.js test, covered

Nothing blocks merge.

github run

@james-elicx
james-elicx marked this pull request as ready for review August 10, 2026 19:11
# Conflicts:
#	packages/vinext/src/shims/internal/app-route-prefetch-policy.ts
#	packages/vinext/src/shims/link.tsx
#	tests/link-navigation.test.ts
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.

1 participant