Skip to content

fix(app-router): reuse runtime segment prefetch payloads#2476

Open
james-elicx wants to merge 9 commits into
codex/fix-segment-cache-prefetch-scheduling-28478866791from
codex/fix-segment-cache-prefetch-runtime-remaining-28478866791
Open

fix(app-router): reuse runtime segment prefetch payloads#2476
james-elicx wants to merge 9 commits into
codex/fix-segment-cache-prefetch-scheduling-28478866791from
codex/fix-segment-cache-prefetch-runtime-remaining-28478866791

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • cache segment-phase runtime prefetch responses for navigation reuse
  • preserve the existing route-tree learning behavior for non-navigation prefetches
  • clean up two floating viewport-prefetch promises in the touched Link shim after the current stacked base merge

Next.js parity

Targets the App Router Segment Cache behavior covered by:

  • test/e2e/app-dir/segment-cache/prefetch-runtime/prefetch-runtime.test.ts

This fixes the run 28478866791 / job 84413308650 row:

  • runtime prefetching > can completely prefetch a page that is fully static

Stacked on #2465 because this row depends on the route-tree -> segment-prefetch scheduling path.

Validation

  • vp test run tests/link-navigation.test.ts -t "Link prefetch scheduling|runtime-prefetch|segment|route-tree|full prefetch" — 42 passed, 18 skipped
  • vp check packages/vinext/src/shims/link.tsx tests/link-navigation.test.ts — clean
  • REPO="$(pwd)" NEXTJS_DIR="/Users/jamesanderson/Developer/vinext/.nextjs-ref" NEXT_TEST_CONCURRENCY=1 ./scripts/run-targeted-nextjs-e2e.sh test/e2e/app-dir/segment-cache/prefetch-runtime/prefetch-runtime.test.ts — 1 passed, 33 skipped

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jul 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2476
npm i https://pkg.pr.new/vinext@2476

commit: de5c237

@github-actions

github-actions Bot commented Jul 1, 2026

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

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: reuse runtime segment prefetch payloads

Reviewed the three-hunk diff in packages/vinext/src/shims/link.tsx. The change is small, well-scoped, and correct. Targeted tests pass (42 passed | 18 skipped) and vp check is clean on the touched file.

What the change does (confirmed correct)

Hunk 1 (cacheForNavigation: mode === "segment") — This makes the segment-phase prefetch entry navigation-reusable while keeping route-tree at false, matching the PR description.

I traced the reuse mechanism end to end to confirm this actually works:

  • The segment request bakes Next-Router-Segment-Prefetch: /_page into the RSC cache-busting hash (createCacheBustingInput, app-rsc-cache-busting.ts:172), so the segment entry's exact rscUrl differs from a navigation request's URL.
  • Navigation reuse still succeeds via the fuzzy-match loop in findPrefetchCacheEntryForNavigation (navigation.ts:455-465), which compares normalizeRscCacheLookupUrl(...) after stripRscCacheBustingSearchParam (navigation.ts:395-403). So the differing /_page hash does not block the match.
  • Reuse is only safe because vinext serves the full unified page payload for /_page (the marker is only a scheduler-phase discriminator, per the comment at link.tsx:527-530), not a partial Next.js segment payload. If that ever changes, this line would need to revert to false.

I also verified the surrounding conditionals are unaffected by flipping segment to true:

  • isOptimisticRouteShellPrefetch (link.tsx:509-510) stays false for segment because of the mode !== "segment" guard.
  • shouldSendSegmentPrefetchHeaders (link.tsx:521) stays false; the /_page header still comes from the dedicated else if (mode === "segment") branch.
  • The new-for-segment navigation-freshness gate (link.tsx:554-559) now short-circuits a segment prefetch when a navigation-reusable entry already exists — this is the desired behavior (a full/nav entry supersedes a segment one).

Hunks 2 & 3 (void prefetchUrl(...)) — Correct floating-promise cleanup at link.tsx:772 and link.tsx:1005. I checked all four prefetchUrl call sites: the remaining unvoided one at link.tsx:963 is properly chained with .catch().finally(), so the file is now consistent.

One gap worth noting (non-blocking)

There is no test asserting the core behavioral change. The existing scheduling tests (tests/link-navigation.test.ts:1320, :1429) assert the /_page request headers but not the resulting entry.cacheForNavigation for the segment-phase entry. The nearby full/loading-shell tests already assert cacheForNavigation (:1780, :1845), so an equivalent assertion for the segment phase — ideally a round-trip showing a segment prefetch is consumed by a subsequent navigation — would lock in this fix and guard against a future regression that silently flips it back to false. The linked Next.js E2E (prefetch-runtime.test.ts) covers this at the integration level, but a unit assertion here would be cheaper insurance.

No correctness blockers. LGTM.

@ask-bonk

ask-bonk Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2476.

Summary

Reviewed the single-file, 3-hunk change in packages/vinext/src/shims/link.tsx. The PR is correct and well-scoped.

Findings:

  1. cacheForNavigation: mode === "segment" — Correct. I traced the reuse path end-to-end:

    • The /_page segment header gets baked into the RSC cache-busting hash, but navigation reuse still works via the fuzzy-match loop in findPrefetchCacheEntryForNavigation (navigation.ts:455-465), which strips the cache-busting param before comparing.
    • Reuse is safe only because vinext serves the full unified payload for /_page (per the comment at link.tsx:527-530), not a partial segment.
    • Verified the surrounding conditionals (isOptimisticRouteShellPrefetch, shouldSendSegmentPrefetchHeaders, the freshness gate) all behave correctly with segment flipped to true.
  2. The two void prefetchUrl(...) additions are correct floating-promise cleanup. All four call sites are now consistent (the remaining unvoided one at line 963 is properly chained with .catch().finally()).

One non-blocking gap: No unit test asserts the core behavioral change — existing tests check the /_page request headers but not the resulting entry.cacheForNavigation for the segment phase. Suggested adding a round-trip assertion to lock the fix in. The Next.js E2E covers it at the integration level.

Verification: targeted tests pass (42 passed, 18 skipped), vp check clean on the touched file. No correctness blockers — LGTM.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

Deploy-suite run 28478866791 / report job 84413308650 triage: this PR appears to cover the App Router fully-static runtime-prefetch row from test/e2e/app-dir/segment-cache/prefetch-runtime/prefetch-runtime.test.ts.

Exact row:

  • runtime prefetching > can completely prefetch a page that is fully static

Since this PR is already James-owned, I am skipping duplicate implementation work for this row. I am not counting broader cacheComponents/use-cache rows here.

@james-elicx james-elicx force-pushed the codex/fix-segment-cache-prefetch-scheduling-28478866791 branch from 4c23523 to f584994 Compare July 1, 2026 13:48
…he-prefetch-runtime-remaining-28478866791

# Conflicts:
#	packages/vinext/src/shims/link.tsx
#	tests/link-navigation.test.ts
…-scheduling-28478866791' into codex/fix-segment-cache-prefetch-runtime-remaining-28478866791

# Conflicts:
#	packages/vinext/src/shims/link.tsx
#	tests/link-navigation.test.ts
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared de5c237 against base 32da0ea 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.8 KB 126.8 KB ⚫ +0.0%
Client entry size (gzip) vinext 120.7 KB 120.7 KB ⚫ +0.0%
Dev server cold start vinext 2.62 s 2.60 s ⚫ -0.7%
Production build time vinext 3.10 s 3.09 s ⚫ -0.4%
RSC entry closure size (gzip) vinext 97.5 KB 97.5 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

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