cloudflare / cloudflare/vinext

Server-side handler for App Shell prefetches (`NEXT_ROUTER_PREFETCH_HEADER: '3'`)

Open
#1,427 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:** [`bf52993`](https://github.com/vercel/next.js/commit/bf529933a2f666185b1075c0897adce49c3579d7)
**PR:** [#93998 — Respond to App Shell prefetches on the server](https://github.com/vercel/next.js/pull/93998)

## What changed

Introduces a new prefetch request type — the **App Shell** — and the server-side handler that renders it. A client (or test harness) can now issue a request with the header `NEXT_ROUTER_PREFETCH_HEADER: '3'` and receive the **param-independent shell** of a route: param- and searchParam-bearing segments suspend, leaving everything that doesn't depend on either intact.

Implementation summary (from the diff):

- A single bit on the prerender store distinguishes an App Shell render from a regular runtime prerender.
- When that bit is set, `await params` and `await searchParams` both **hang forever** (never resolve), so any segment that depends on either suspends to its nearest fallback. The "shell" of the route is whatever renders above those Suspense boundaries — layouts, cookie-derived UI, other request-context-derived content.
- New plumbing across:
- `server/app-render/app-render.tsx` (+34/-8) — branch on the new request type
- `server/app-render/work-unit-async-storage.external.ts` (+8) — new flag in prerender store
- `server/base-server.ts` (+4/-2) — dispatch the new prefetch type
- `server/request/params.ts` (+25/-1) and `server/request/search-params.ts` (+14) — make awaits hang under App Shell
- `shared/lib/router/utils/cache-busting-search-param.ts` (+3/-3) — cache-busting param handling for the new type

**Static shell prefetches are explicitly out of scope** for this PR; they will use a different "rewinding" strategy on the existing static prefetch response.

**No client-side changes yet** — only server.

## Impact on vinext

vinext serves App Router RSC responses through `entries/app-rsc-entry.ts` and the App Router production server. Today, vinext recognizes prefetch headers (`Next-Router-Prefetch`) but treats them as a single boolean — there is no awareness of the `'3'` value or any concept of an App Shell render.

To support App Shell prefetches, vinext would need to:

1. **Recognize `NEXT_ROUTER_PREFETCH_HEADER: '3'`** on incoming requests and branch the render path accordingly.
2. **Suspend `params` and `searchParams` accesses** during the render — i.e., the thenables returned by our `params`/`searchParams` machinery must hang forever (never resolve) for this request type. This is invasive: it requires plumbing a per-request "App Shell mode" flag into the request context that both the params thenable factory and the searchParams thenable factory consult.
3. **Stream the resulting RSC payload** with the suspended portions rendered as their fallbacks (layouts, loading.tsx boundaries, etc.).
4. **Cache the App Shell response** keyed by route (not by params/searchParams) — the whole point is one shell per route, served instantly for any concrete navigation.

Open questions for vinext:

- Where does the App Shell live in our cache layer (KV / ISR cache / a new tier)?
- How does this interact with our prefetch shim in `shims/next-navigation.ts` — does the client-side `router.prefetch()` need a new mode, or is App Shell prefetching always automatic?
- What is the dev-mode behavior — render on demand, or skip entirely?

Behavior implementation is a large workstream and depends on all the Sparkle co-flags (see #1405 for the validation list). This issue tracks the **server-side render branch** specifically.

## Related

- #1405 — `experimental.appShells` config flag (plumbing, blocked by all Sparkle flags)
- #860 — `experimental.prefetchInlining` default flip (same workstream)
- Sparkle defaults tracking issue (varyParams, optimisticRouting, cachedNavigations)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.