Skip to content

fix(app-router): limit mounted slot cache variants - #2497

Merged
james-elicx merged 1 commit into
mainfrom
codex/limit-mounted-slot-cache-variants
Jul 2, 2026
Merged

fix(app-router): limit mounted slot cache variants#2497
james-elicx merged 1 commit into
mainfrom
codex/limit-mounted-slot-cache-variants

Conversation

@james-elicx

@james-elicx james-elicx commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • bypass persistent App Router RSC ISR cache reads and writes for mounted-slot variants
  • keep HTML-triggered RSC regeneration on the canonical no-mounted-slots key
  • keep mounted slots in the public RSC Vary/header-hash variant surface for Next.js-shaped CDN behavior
  • preserve fresh mounted-slot RSC rendering semantics, including live request search params

Tests

  • vp test run tests/app-rsc-cache-busting.test.ts
  • vp test run tests/app-page-cache.test.ts tests/app-page-dispatch.test.ts tests/app-rsc-cache-busting.test.ts tests/app-rsc-request-normalization.test.ts tests/isr-cache.test.ts
  • vp check packages/vinext/src/server/app-page-cache.ts packages/vinext/src/server/app-page-cache-finalizer.ts packages/vinext/src/server/app-rsc-cache-busting.ts tests/app-page-cache.test.ts tests/app-page-dispatch.test.ts tests/app-rsc-cache-busting.test.ts

Review

  • independent review completed with no remaining findings
  • Bonk review feedback addressed

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2497
npm i https://pkg.pr.new/vinext@2497

commit: ee367b5

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

The review is complete. Let me summarize my findings. The changes are correct and well-tested; I found no blocking issues, only minor observations.

Review Summary

I reviewed PR #2497 (fix(app-router): limit mounted slot cache variants). The change is sound and well-tested. Verified:

  • Touched tests pass (134 tests) plus related suites (316 tests).
  • vp check clean on all three modified source files (format, lint, types).
  • Dev/prod parity: the write-side guard in scheduleAppPageRscCacheWrite and the read-side guard in readAppPageCacheResponse both live in shared server/* helpers that the dispatch layer (app-page-dispatch.ts) and HTML finalizer (app-page-cache-finalizer.ts) all route through, so there's no separate unguarded RSC write path.

What the change does (correctly)

  1. Read guard (app-page-cache.ts:333) — RSC requests with a mounted-slots header now short-circuit to MISS before touching isrGet/isrRscKey, so slot variants never read persistent cache.
  2. Write guard (app-page-cache-finalizer.ts:254) — same variants bail out of persistent RSC writes.
  3. Canonical HTML-triggered regen (app-page-cache.ts:439) — now writes the RSC key with null mounted slots instead of the request's header. This is a real hardening win: a forged X-Vinext-Mounted-Slots header on an HTML request can no longer poison the canonical RSC cache entry (covered by the slot:forged:/ test).
  4. Vary headerVINEXT_MOUNTED_SLOTS_HEADER removed from VINEXT_RSC_VARY_HEADER. This is consistent with the existing client-reuse-manifest precedent: since variants are no longer persistently cached and the header still feeds the _rsc cache-busting hash (app-rsc-cache-busting.ts:185), URLs still differentiate per slot state, making the Vary entry redundant.

Minor observations (non-blocking)

  • Dead argument: scheduleAppPageRscCacheWrite still passes options.mountedSlotsHeader to isrRscKey at app-page-cache-finalizer.ts:260, but the early return at line 254 guarantees it's always falsy there. Likewise readAppPageCacheResponse:341 now only reaches isrRscKey with a falsy header. Both are harmless (defensive/no-op), but could be simplified to null to make the invariant explicit and avoid future confusion.
  • Behavioral note (improvement, not a bug): the dispatch test change shows fresh-rendered mounted-slot RSC requests now correctly carry request search params (tab=popular) instead of serving the stale empty-search variant. Worth a one-line mention in the PR body, since it's a user-visible correctness change beyond "just caching."

No correctness, parity, or security concerns found. LGTM.

github run

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared b5d10cb against base 20e1076 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 126.3 KB 126.3 KB ⚫ +0.0%
Client entry size (gzip) vinext 120.4 KB 120.4 KB ⚫ 0.0%
Dev server cold start vinext 2.60 s 2.57 s ⚫ -1.0%
Production build time vinext 3.25 s 3.27 s ⚫ +0.6%
RSC entry closure size (gzip) vinext 97.9 KB 97.9 KB ⚫ +0.0%
Server bundle size (gzip) vinext 164.4 KB 164.4 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx
james-elicx force-pushed the codex/limit-mounted-slot-cache-variants branch from 75107ae to e081409 Compare July 2, 2026 22:30
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Bonk review — PR #2497 fix(app-router): limit mounted slot cache variants

Reviewed the full diff, traced the read/write cache paths, verified dev/prod parity, and ran the touched suites. No blocking issues — LGTM.

Verification

  • vp test run tests/app-page-cache.test.ts tests/app-page-dispatch.test.ts tests/app-rsc-cache-busting.test.ts → 134 passed.
  • Traced both guarded paths to their sole callers so there is no unguarded RSC write path:
    • Read guard readAppPageCacheResponse (app-page-cache.ts:333) ← app-page-dispatch.ts:642.
    • Write guard scheduleAppPageRscCacheWrite (app-page-cache-finalizer.ts:254) ← finalizeAppPageRscCacheResponse (app-page-cache-finalizer.ts:229).

What the change does correctly

  1. Read guard short-circuits mounted-slot RSC requests to MISS before isrGet/isrRscKey, so slot variants never read persistent cache — and a pre-deploy slot-keyed entry can't be served either.
  2. Write guard bails mounted-slot variants out of persistent RSC writes.
  3. Canonical HTML-triggered regen (app-page-cache.ts:439) now writes the RSC key with null mounted slots. This is real hardening: a forged X-Vinext-Mounted-Slots header on an HTML request can no longer poison the canonical RSC entry. The slot:forged:/ test (tests/app-page-cache.test.ts:655) confirms the write lands on rsc:/stale-html:none. The pre-existing HTML finalizer path (app-page-cache-finalizer.ts:132) already used null, so both HTML→RSC write paths agree.
  4. Vary header drops VINEXT_MOUNTED_SLOTS_HEADER. Correct: the header still feeds the _rsc cache-busting hash (app-rsc-cache-busting.ts:185), so slot state still differentiates URLs; the Vary entry was redundant now that variants aren't persistently cached.

Truthiness-vs-normalization check (verified consistent)

The guards test raw options.mountedSlotsHeader truthiness, while appIsrRscKey runs normalizeMountedSlotsHeader (which nulls malformed values). These can't diverge in practice: the pipeline normalizes the header at request-normalization (app-rsc-request-normalization.ts:133) before it reaches either helper, so by the time the guards run the value is already a valid normalized string or null.

Minor (non-blocking)

  • readAppPageCacheResponse:341 still passes options.mountedSlotsHeader into isrRscKey, but the early return at :333 guarantees it's falsy for RSC requests by then. Harmless/no-op; could be null to make the invariant explicit, matching the null you already pass at :439 and in the write path. Pure readability, not required.
  • The dispatch test change (tab=popular search params on fresh-rendered slot variants) is a user-visible correctness improvement beyond caching — worth a one-line PR-body mention.

No correctness, parity, or security concerns.

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2497.

Summary

Reviewed fix(app-router): limit mounted slot cache variants. The change is correct, well-tested, and preserves dev/prod parity — no blocking issues.

Verified:

  • 134 tests pass across the three touched suites.
  • Both guards (read at app-page-cache.ts:333, write at app-page-cache-finalizer.ts:254) live in shared server/* helpers with single callers, so there's no unguarded RSC cache path.
  • The forged-header test confirms the canonical RSC key (rsc:/stale-html:none) is written even with a spoofed X-Vinext-Mounted-Slots header — a real cache-poisoning hardening win.
  • Removing the header from Vary is safe: it still feeds the _rsc cache-busting hash, so URLs still differentiate per slot state.
  • Checked a potential truthiness-vs-normalization mismatch between the guards and appIsrRscKey — confirmed consistent because the header is normalized upstream at request-normalization before reaching either helper.

Minor (non-blocking): one dead mountedSlotsHeader argument that's a guaranteed no-op after the early return (could be null for clarity), and a suggestion to mention the tab=popular search-param correctness improvement in the PR body.

github run

@james-elicx
james-elicx force-pushed the codex/limit-mounted-slot-cache-variants branch from e081409 to b5d10cb Compare July 2, 2026 22:35
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonk review — PR #2497 fix(app-router): limit mounted slot cache variants

Traced both cache paths to their callers, verified header/normalization invariants, checked dev/prod parity, and ran the touched suites plus related response/ISR tests. No blocking issues — LGTM.

Verification

  • vp test run tests/app-page-cache.test.ts tests/app-page-dispatch.test.ts tests/app-rsc-cache-busting.test.ts tests/app-rsc-request-normalization.test.ts → 196 passed.
  • vp test run tests/app-page-response.test.ts tests/app-rsc-response-finalizer.test.ts tests/isr-cache.test.ts → 110 passed.
  • vp check clean (format + lint + types) on all three modified source files.

What the change does correctly

  1. Read guard (app-page-cache.ts:333) short-circuits mounted-slot RSC requests to MISS before isrGet/isrRscKey, and falls through to a fresh render. The early return skips clearRequestContext(), which is correct and consistent with the other MISS returns in this function — the request context must stay live for the fresh render downstream.
  2. Write guard (app-page-cache-finalizer.ts:254) bails mounted-slot variants out of persistent RSC writes. Verified parity: the other caller of finalizeAppPageRscCacheResponse (app-page-render.ts:874, the fresh-render path) passes mountedSlotsHeader through (:903), so it inherits the same guard — there is no unguarded RSC write path.
  3. Canonical HTML-triggered regen (app-page-cache.ts:439) now writes the RSC key with null mounted slots. Real hardening: a forged X-Vinext-Mounted-Slots header on an HTML request can no longer poison the canonical RSC entry. Matches the pre-existing HTML finalizer path (app-page-cache-finalizer.ts:132), so both HTML→RSC write paths agree.
  4. Vary header drops VINEXT_MOUNTED_SLOTS_HEADER. Safe: the header still feeds the _rsc cache-busting hash (app-rsc-cache-busting.ts:185), so slot state still differentiates the request URL; the Vary entry was redundant now that variants are never persistently cached and can't be cross-served by a URL-keyed CDN.

Invariant checks (verified consistent)

  • Truthiness vs. normalization: the guards test raw options.mountedSlotsHeader truthiness while appIsrRscKey runs normalizeMountedSlotsHeader. These can't diverge because the pipeline normalizes the header at request-normalization (app-rsc-request-normalization.ts:133normalizeMountedSlotsHeader, which returns null for empty/malformed input, app-mounted-slots-header.ts:53) before it reaches either helper. By the time the guards run, the value is already a valid normalized string or null.
  • CDN header behavior change (intended, verified by tests): before this PR, a production mounted-slot RSC MISS render had capturedRscDataPromise set (shouldCaptureRscForCacheMetadata does not gate on slots), so scheduleAppPageRscCacheWrite returned true and applyPendingDynamicCdnHeaders was applied (CDN cache-tags + X-Vinext-Cache: MISS). Now the mounted-slots guard makes didSchedule false, so finalizeAppPageRscCacheResponse returns the response untouched and those shared-cache CDN headers are no longer emitted. This is correct — mounted-slot responses are per-client and must not carry shared-cache tag/state headers — and is exactly what the dispatch test now asserts (x-vinext-cachenull).

Minor (non-blocking)

  • readAppPageCacheResponse still passes options.mountedSlotsHeader into isrRscKey at :341, but the early return at :333 guarantees it's falsy for RSC requests by then. Harmless no-op; could be null to make the invariant explicit and match the null already passed at :439 and in the write path. Pure readability.
  • The dispatch test change (fresh-rendered slot variants now carry request search params, e.g. tab=popular) is a user-visible correctness improvement beyond caching — worth a one-line PR-body mention.

No correctness, parity, or security concerns.

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2497.

Summary

Reviewed fix(app-router): limit mounted slot cache variants. The change is correct, well-tested, and preserves dev/prod parity — no blocking issues.

Verified:

  • 196 tests pass across the four touched suites; 110 more across response/ISR suites; vp check clean on all three source files.
  • Both guards live in shared server/* helpers. I confirmed parity by tracing the second caller of finalizeAppPageRscCacheResponse (app-page-render.ts:874, the fresh-render path), which passes mountedSlotsHeader through and inherits the same guard — no unguarded RSC write path.
  • The truthiness-vs-normalization question is safe: the header is normalized upstream at request-normalization (normalizeMountedSlotsHeader returns null for malformed input) before reaching either guard.
  • Removing the header from Vary is safe because it still feeds the _rsc cache-busting hash, so URLs still differentiate per slot state.

Notable finding I dug into: a real (intended) behavioral change — mounted-slot RSC MISS responses no longer receive applyPendingDynamicCdnHeaders (CDN cache-tags + X-Vinext-Cache: MISS), because scheduleAppPageRscCacheWrite now returns false early and finalizeAppPageRscCacheResponse returns the response untouched. This is correct (these are per-client, non-cached responses) and is exactly what the updated dispatch test asserts.

Minor (non-blocking): the dead mountedSlotsHeader arg passed into isrRscKey at app-page-cache.ts:341 (guaranteed falsy after the early return — could be null for clarity), and a suggestion to mention the tab=popular search-param correctness improvement in the PR body.

github run

@james-elicx
james-elicx marked this pull request as ready for review July 2, 2026 22:41
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonk review — PR #2497 fix(app-router): limit mounted slot cache variants

Traced both cache paths to all callers, verified the header-normalization and Vary/cache-busting invariants, checked dev/prod parity across every RSC cache entry point, and ran the touched suites. No blocking issues — LGTM.

Verification

  • vp test run tests/app-page-cache.test.ts tests/app-page-dispatch.test.ts tests/app-rsc-cache-busting.test.ts tests/app-rsc-request-normalization.test.ts196 passed.
  • vp check clean (format + lint + types) on all three modified source files.
  • Confirmed there is no unguarded RSC cache read/write path: readAppPageCacheResponse, scheduleAppPageRscCacheWrite, and finalizeAppPageRscCacheResponse live only in the shared server/app-page-* helpers, and every caller (app-page-dispatch.ts, app-page-render.ts:874 fresh-render path, app-server-action-execution.ts action re-render via buildPageElement) routes through them and forwards mountedSlotsHeader. App Router prod/worker entries delegate to the built RSC entry, so they inherit the guards.

What the change does correctly

  1. Read guard (app-page-cache.ts:333) short-circuits mounted-slot RSC requests to MISS before isrGet/isrRscKey, then falls through to a fresh render. The early return correctly skips clearRequestContext() (the request context must stay live for the downstream fresh render), consistent with the other MISS returns in this function.
  2. Write guard (app-page-cache-finalizer.ts:254) bails mounted-slot variants out of persistent RSC writes; the sole other caller (app-page-render.ts:903) inherits it.
  3. Canonical HTML-triggered regen (app-page-cache.ts:439) now writes the RSC key with null mounted slots. Real hardening: the slot:forged:/ test (tests/app-page-cache.test.ts:655) confirms the write lands on rsc:/stale-html:none, so a forged X-Vinext-Mounted-Slots header on an HTML request can no longer poison the canonical RSC entry. Matches the pre-existing HTML finalizer path (app-page-cache-finalizer.ts:132), so both HTML→RSC write paths agree.
  4. Vary header drops VINEXT_MOUNTED_SLOTS_HEADER. Safe: the header still feeds the _rsc cache-busting hash (app-rsc-cache-busting.ts:185), so slot state still differentiates the request URL; the Vary entry was redundant now that variants are never persistently cached. The VINEXT_MOUNTED_SLOTS_HEADER import remains used (lines 185, 299), so no dead import.

Invariant checks (verified consistent)

  • Truthiness vs. normalization: the guards test raw options.mountedSlotsHeader truthiness while appIsrRscKey runs normalizeMountedSlotsHeader. These can't diverge — the pipeline normalizes the header once at request-normalization (app-rsc-request-normalization.ts:133normalizeMountedSlotsHeader, which returns null for empty/malformed input) before it reaches any of these helpers. By the time the guards run, the value is already a valid normalized string or null.
  • CDN header behavior change (intended, verified by tests): a mounted-slot RSC MISS render now returns untouched from finalizeAppPageRscCacheResponse (didSchedule is false), so it no longer emits applyPendingDynamicCdnHeaders (CDN cache-tags + X-Vinext-Cache: MISS). This is correct — those responses are per-client and must not carry shared-cache tag/state headers — and is exactly what the updated dispatch test asserts (x-vinext-cachenull).

Minor (non-blocking)

  • readAppPageCacheResponse:341 still passes options.mountedSlotsHeader into isrRscKey, but the early return at :333 guarantees it's falsy for RSC requests by then. Harmless no-op; could be null to make the invariant explicit and match the null already passed at :439 and in the write path. Pure readability.
  • The dispatch test change (fresh-rendered slot variants now carry request search params, e.g. tab=popular, instead of serving a stale empty-search variant) is a user-visible correctness improvement beyond caching — worth a one-line PR-body mention.

No correctness, parity, or security concerns.

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2497.

Summary

Reviewed fix(app-router): limit mounted slot cache variants. The change is correct, well-tested, and preserves dev/prod parity — no blocking issues.

Verified:

  • 196 tests pass across the four touched suites; vp check clean on all three source files.
  • No unguarded RSC cache path — all reads/writes route through the shared server/app-page-* helpers, and every caller (dispatch, fresh-render at app-page-render.ts:874, and server-action re-render via buildPageElement) forwards mountedSlotsHeader so it inherits the guards.
  • The forged-header test confirms the canonical RSC key (rsc:/stale-html:none) is written even with a spoofed X-Vinext-Mounted-Slots header — a real cache-poisoning hardening win.
  • Removing the header from Vary is safe: it still feeds the _rsc cache-busting hash, so URLs still differentiate per slot state. The VINEXT_MOUNTED_SLOTS_HEADER import is still used (no dead import).
  • The truthiness-vs-normalization question is safe because the header is normalized once upstream at request-normalization before reaching any guard.
  • The CDN-header behavior change (mounted-slot MISS responses no longer emit shared-cache tags/X-Vinext-Cache) is intended and asserted by the updated dispatch test.

Minor (non-blocking): one guaranteed-no-op mountedSlotsHeader argument at app-page-cache.ts:341 (could be null for clarity), and a suggestion to mention the tab=popular search-param correctness improvement in the PR body.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

Follow-up on the latest Bonk minor notes: current head already passes null into isrRscKey in both read/write paths, and the PR body already calls out the fresh mounted-slot request search params behavior. No code changes needed from that pass.

@james-elicx
james-elicx force-pushed the codex/limit-mounted-slot-cache-variants branch from b5d10cb to ee367b5 Compare July 2, 2026 23:01
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

The test changes are meaningful and strengthen the assertions:

  • Asserts isrGet/isrSet are never called for mounted-slot RSC requests (proving no persistent read/write).
  • Asserts fresh flight render with live search params (tab=popular) instead of stale empty-search variant.
  • Asserts no CDN cache-state header (x-vinext-cache → null).

These are proper positive assertions, not weakened. The review is complete.

Review Summary

I reviewed PR #2497 (fix(app-router): limit mounted slot cache variants). The change is correct, well-tested, and preserves dev/prod parity — no blocking issues.

Verified:

  • Tests pass: 97 tests across the two touched suites; vp check clean (format + lint + types) on both modified source files.
  • Read guard (app-page-cache.ts:333): mounted-slot RSC requests short-circuit to MISS before isrGet/isrRscKey, then fall through to fresh render. Correctly skips clearRequestContext() so the context stays live for the downstream render — consistent with other MISS returns.
  • Write guard (app-page-cache-finalizer.ts:254): mounted-slot variants bail out of persistent RSC writes. Confirmed no unguarded write path — the only other caller (app-page-render.ts:874, fresh-render) forwards mountedSlotsHeader (line 903) and inherits the guard.
  • Canonical HTML-triggered regen (app-page-cache.ts:439): now writes the RSC key with null mounted slots — a real cache-poisoning hardening win against a forged X-Vinext-Mounted-Slots header.
  • null args are already applied in both read/write paths (the earlier Bonk "dead argument" minor note is resolved on current head), making the invariant explicit.
  • Test changes strengthen assertions: they now assert isrGet/isrSet are never called for slot variants, fresh render carries live search params (tab=popular), and x-vinext-cache is null — not weakened.

One documentation discrepancy (non-blocking, no code impact):

  • The PR body says "keep mounted slots in the public RSC Vary/header-hash variant surface" — this is accurate: VINEXT_MOUNTED_SLOTS_HEADER remains in VINEXT_RSC_VARY_HEADER (app-rsc-cache-busting.ts:42), and that file is untouched in this PR. However, several of the earlier ask-bonk review comments on this PR incorrectly claimed the header was "dropped from Vary." Those review comments were mistaken; the code is correct and consistent (variants aren't persistently cached, but the header still feeds the _rsc cache-busting hash and stays in Vary for Next.js-shaped CDN behavior). No code change needed — flagging only so the inaccurate prior-review claim isn't mistaken for the source of truth.

No correctness, parity, or security concerns. LGTM.

github run

@james-elicx
james-elicx merged commit 5915c87 into main Jul 2, 2026
63 checks passed
@james-elicx
james-elicx deleted the codex/limit-mounted-slot-cache-variants branch July 2, 2026 23:05
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
james-elicx added a commit that referenced this pull request Aug 3, 2026
* fix(app-router): keep mounted-slot RSC MISS responses no-store

finalizeAppPageRscCacheResponse derived "should I rewrite the client
headers?" from the return value of scheduleAppPageRscCacheWrite. Those are
independent decisions, and #2497 made them disagree: mounted-slot variants
now correctly skip the persistent write (their RSC key is slot-blind), but
the early return took the pending-dynamic finalization with it.

The result is that a fresh ISR-eligible RSC MISS carrying
X-Vinext-Mounted-Slots leaves the origin with its initial
`s-maxage=..., stale-while-revalidate` instead of being rewritten to
`no-store, must-revalidate`. That header is what stops a shared cache from
storing a stream that may still reach cookies()/headers() below a Suspense
boundary after the cache policy was chosen, so a personalized payload can be
stored and replayed for the URL/variant. Apps with named parallel routes send
the header on essentially every client navigation; apps without slots never
enter the path.

Gate the header rewrite on preserveClientResponseHeaders alone, which is
already `cacheState !== "MISS"` at the only production call site. This
restores the client-facing behavior that shipped before #2497 while keeping
its cache-write change, and matches finalizeAppPageHtmlCacheResponse, which
never coupled the two. Doing it structurally rather than adding a
mountedSlotsHeader term means the next early return added to
scheduleAppPageRscCacheWrite cannot silently reintroduce this.

* fix(app-router): keep mounted slots out of edge caches

* fix(cache): clear mounted-slot CDN overrides

* docs(cache): explain mounted-slot no-store scope

* fix(cache): clear pending CDN overrides

* docs(cache): clarify pending header policy

* test(cache): cover dynamic mounted-slot headers

* test(cache): cover pending HTML CDN overrides

---------

Co-authored-by: James <james@eli.cx>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant