Skip to content

App Router dynamic Flight payload: advertise per-segment prefetching when cacheComponents is on (not only during static generation) #2811

Description

@github-actions

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:

  1. 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.
  2. 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.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    nextjs-trackingTracking issue for a Next.js canary change relevant to vinext

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions