feat(app-router): give shared App page dispatches a query-free cache identity - #3462
james-elicx wants to merge 3 commits into
Conversation
|
/bigbonk review for issues Please review exact head |
@vinext/cloudflare
create-vinext-app
@vinext/types
vinext
@cloudflare/workers-response-store
commit: |
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
| /** Drop every search pair except the internal `_rsc` representation selector. */ | ||
| export function keepOnlyRscCacheBustingSearchParam(url: URL): void { | ||
| const rawQuery = url.search.startsWith("?") ? url.search.slice(1) : url.search; | ||
| const pairs = rawQuery.split("&").filter((pair) => isRscCacheBustingSearchPair(pair)); |
There was a problem hiding this comment.
[P2] Canonicalize _rsc before using it in the query-free identity. This keeps every raw matching pair, including on non-RSC HTML requests where _rsc is not a representation selector at all. For example, repeated requests to a static page as /?_rsc=<random UUID> each get a distinct shared key and can be admitted because the page never reads searchParams; valid RSC requests can likewise append ignored duplicate/encoded _rsc pairs after the first validated value. That leaves cache cardinality attacker-controlled and defeats the query-free key. Drop _rsc for non-RSC identities, and retain one canonicalized validated pair for RSC identities.
There was a problem hiding this comment.
Fixed in the commit "keep only the validated _rsc value in the query-free cache identity" (now 65062f2 after rebasing). HTML identities drop every _rsc pair. RSC identities keep exactly one _rsc: the value resolveInvalidRscCacheBustingRequest validated, read the same way (the first pair) and written back with setRscCacheBustingSearchParam. Duplicate and encoded pairs are ignored. Tests cover an HTML request carrying _rsc, a rewritten RSC request with duplicate and encoded pairs, and a table of helper cases.
|
Posted 1 actionable inline finding. Targeted tests and |
c7673ae to
65062f2
Compare
|
/bigbonk review for issues Please review exact head |
|
Targeted tests and |
65062f2 to
f363914
Compare
|
Re the P2 in the last review (RSC-only headers in HTML identities): fixed in f363914. For HTML requests, |
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
f363914 to
9f29f44
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
9f29f44 to
93f840e
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
93f840e to
14199f8
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
14199f8 to
b7f746c
Compare
|
LGTM! |
1510945 to
8188454
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
8188454 to
a9fa00f
Compare
a9fa00f to
1b99f89
Compare
1b99f89 to
217c83c
Compare
217c83c to
7b5cd37
Compare
0ac6064 to
844a5b2
Compare
844a5b2 to
ef1f5a2
Compare
…identity Adapters that declare responseStageCacheIdentity: "query-free" behind completed-response admission receive options.cacheIdentity for shared App page GET/HEAD dispatches. The identity drops the user query from the URL and resolvedUrl, keeping _rsc, the .rsc suffix and the render mode, while the dispatched request keeps the real query for the render. It is omitted under a next.config public cache policy and for bypassed, interception and mounted-slot dispatches.
… cache identity HTML identities drop _rsc, which selects no representation there. RSC identities keep the one value the cache-busting check validated, re-serialized, so duplicate or encoded _rsc pairs can't grow the shared key space.
HTML dispatch fixes its render mode to navigation and reads no other RSC selector outside the props, so an HTML identity no longer carries the Vary fields transports key, such as Next-Url or router state.
ef1f5a2 to
4a45af5
Compare
Stacked on #3461. First half of plan PR 4 (transport contract).
Problem
A shared response-stage transport had to key App pages by the complete request URL, query included. Next.js keys App page ISR entries without the query, so every query of a static page shares one entry. Keying by the full URL meant one entry per query, which stores more than Next.js.
Change
VinextResponseStageDispatchOptionsgainscacheIdentity?: { request, props }.props.resolvedUrl._rsc, the.rscsuffix and the render mode.responseStageCacheIdentity: "query-free"on itsCdnCacheAdapter. Core honours this only when the adapter also hasrequiresCompletedResponseAdmission. That admission step (feat(app-router): admit rendered App pages only with a negative searchParams proof #3461) refuses App pages without a negativesearchParamsproof, and it's what makes the shared key safe.next.configpublic cache policy, which Next.js caches per URL;multi-stage.tsis updated.Tests
tests/app-rsc-handler.test.ts:?_rsc, the loading-shell_rsc=<hash>and.rsc?_rsc.