cloudflare / cloudflare/vinext
App Router dynamic Flight payload: advertise per-segment prefetching when `cacheComponents` is on (not only during static generation)
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
## Next.js Change
**Commit:** [`4be2ee7`](https://github.com/vercel/next.js/commit/4be2ee7443820454c5c7b802cc8bfed516856939)
**PR:** [#96583](https://github.com/vercel/next.js/pull/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:
```ts
// 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
- #1614 — App Router: client-side App Shell prefetching (segment cache scheduler, shell vary-path, fulfilled-first navigation lookup)
- #1938 — App Router runtime render: include `partialPrefetching` in route renderOpts so initial HTML emits the `SubtreeHasPartialPrefetching` hint
- #1819 — Support `partialPrefetching` global config and `unstable_prefetch = 'partial'` segment opt-in
Contributor guide
Research direction
Start in vinext's App Router RSC implementation, especially entries/app-rsc-entry.ts and the server/app-*.ts files, and locate where the dynamic Flight payload emits the S flag. Compare that value with the initial payload path and verify it includes cacheComponents alongside isStaticGeneration. Port the coverage from test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts so a dynamic navigation followed by router.prefetch() does not trigger a fallback request.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, typescript
- Domain
- backend, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100