cloudflare / cloudflare/vinext

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

Open
#2,811 0 comments 0 reactions 0 assignees View on GitHub
nextjs-tracking
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.