Next.js Change
Commit: 4be2ee7
PR: #96583
What changed
Fixes the dynamic Flight payload so it correctly advertises per-segment prefetching support. Previously, the dynamic RSC response only set the "supports per-segment prefetching" flag during static generation. As a result, after a dynamic navigation a subsequent router.prefetch() could fall back to loading-boundary prefetching and issue an unnecessary request, even on a Cache Components route where per-segment prefetching is always supported.
Mechanism (from the diff)
In generateDynamicRSCPayload, the S field of the Flight payload changed:
// packages/next/src/server/app-render/app-render.tsx
// before:
S: workStore.isStaticGeneration,
// after:
// Tells the client whether this route supports per-segment prefetching.
// With Cache Components, all routes support it. Without it, only fully
// static pages do, because their per-segment prefetch responses are
// generated during static generation (build or ISR).
S: workStore.isStaticGeneration || ctx.renderOpts.cacheComponents,
The S flag is what the client reads to decide whether a route supports per-segment prefetching. Under Cache Components every route supports it, but the initial/dynamic payload only reported true when the render was a static generation. This made the client downgrade a later router.prefetch() to loading-boundary prefetching. The fix ORs in ctx.renderOpts.cacheComponents so dynamic responses on CC routes also advertise the capability.
This matches the equivalent logic already applied elsewhere in app-render.tsx (the PR notes it was correctly determined at one call site but not in the initial/dynamic payload).
Impact on vinext
vinext generates the App Router RSC/Flight payload in its App Router server (entries/app-rsc-entry.ts / server/app-*.ts). Wherever vinext emits the per-segment-prefetch-support flag (S) in the dynamic Flight payload, it must be isStaticGeneration || cacheComponents, not isStaticGeneration alone. Otherwise, on a Cache Components route, a router.prefetch() issued after a dynamic navigation will incorrectly fall back to loading-boundary prefetching and make an extra request.
What to check/do:
- Set the per-segment-prefetch flag to
isStaticGeneration || cacheComponents in the dynamic RSC payload generator, so dynamic responses on CC routes advertise per-segment prefetching.
- Keep parity across payload paths. Verify the same value is used both in the initial payload and in the dynamic-navigation payload — the bug here was that the two paths disagreed.
- Port the test. Next.js extended
test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts to assert that after a dynamic navigation, a subsequent router.prefetch() still uses per-segment prefetching (no fallback request).
Notes
- This is specific to Cache Components (
cacheComponents) behavior. It only matters once vinext supports per-segment prefetching and Cache Components routes — closely related to the segment-cache / App Shell tracking work.
- Without Cache Components, only fully static pages advertise per-segment prefetching, which the existing
isStaticGeneration value already handles.
Related
Next.js Change
Commit:
4be2ee7PR: #96583
What changed
Fixes the dynamic Flight payload so it correctly advertises per-segment prefetching support. Previously, the dynamic RSC response only set the "supports per-segment prefetching" flag during static generation. As a result, after a dynamic navigation a subsequent
router.prefetch()could fall back to loading-boundary prefetching and issue an unnecessary request, even on a Cache Components route where per-segment prefetching is always supported.Mechanism (from the diff)
In
generateDynamicRSCPayload, theSfield of the Flight payload changed:The
Sflag is what the client reads to decide whether a route supports per-segment prefetching. Under Cache Components every route supports it, but the initial/dynamic payload only reportedtruewhen the render was a static generation. This made the client downgrade a laterrouter.prefetch()to loading-boundary prefetching. The fix ORs inctx.renderOpts.cacheComponentsso dynamic responses on CC routes also advertise the capability.This matches the equivalent logic already applied elsewhere in
app-render.tsx(the PR notes it was correctly determined at one call site but not in the initial/dynamic payload).Impact on vinext
vinext generates the App Router RSC/Flight payload in its App Router server (
entries/app-rsc-entry.ts/server/app-*.ts). Wherever vinext emits the per-segment-prefetch-support flag (S) in the dynamic Flight payload, it must beisStaticGeneration || cacheComponents, notisStaticGenerationalone. Otherwise, on a Cache Components route, arouter.prefetch()issued after a dynamic navigation will incorrectly fall back to loading-boundary prefetching and make an extra request.What to check/do:
isStaticGeneration || cacheComponentsin the dynamic RSC payload generator, so dynamic responses on CC routes advertise per-segment prefetching.test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.tsto assert that after a dynamic navigation, a subsequentrouter.prefetch()still uses per-segment prefetching (no fallback request).Notes
cacheComponents) behavior. It only matters once vinext supports per-segment prefetching and Cache Components routes — closely related to the segment-cache / App Shell tracking work.isStaticGenerationvalue already handles.Related
partialPrefetchingin route renderOpts so initial HTML emits theSubtreeHasPartialPrefetchinghint #1938 — App Router runtime render: includepartialPrefetchingin route renderOpts so initial HTML emits theSubtreeHasPartialPrefetchinghintpartialPrefetchingglobal config andunstable_prefetch = 'partial'segment opt-in #1819 — SupportpartialPrefetchingglobal config andunstable_prefetch = 'partial'segment opt-in