fix: preserve multi-valued Set-Cookie headers in route handler ISR cache#758
fix: preserve multi-valued Set-Cookie headers in route handler ISR cache#758NathanDrake2406 wants to merge 1 commit intocloudflare:mainfrom
Conversation
buildAppRouteCacheValue() used headers.forEach() which overwrites same-key entries, causing all but the last Set-Cookie header to be silently dropped from cached route handler responses. Uses getSetCookie() to preserve all Set-Cookie headers as a string array, matching the pattern used elsewhere in the codebase (worker-utils.ts, prod-server.ts).
commit: |
|
/bigbonk review |
There was a problem hiding this comment.
Clean fix. The bug analysis is correct: Headers.forEach() joins multi-valued headers into a single comma-separated string for each key, which is the wrong thing for Set-Cookie (cookies with ; attributes get mangled). The fix correctly uses getSetCookie() to preserve each cookie as a separate array entry, matching the existing pattern throughout the codebase (worker-utils.ts, prod-server.ts, deploy.ts).
The type is already Record<string, string | string[]> so the string[] storage is type-safe, the restore path in buildRouteHandlerCachedResponse already handles Array.isArray(value) with append(), and the KV serialization layer round-trips string[] through JSON without issue.
Tests cover the three key scenarios (multiple cookies preserved, zero cookies = no key, full round-trip through store/restore). LGTM.
| if (key === "set-cookie" || key === "x-vinext-cache" || key === "cache-control") return; | ||
| headers[key] = value; | ||
| }); | ||
| const setCookies = response.headers.getSetCookie?.() ?? []; |
There was a problem hiding this comment.
Nit: the optional chaining (getSetCookie?.()) is defensive against environments where Headers doesn't have getSetCookie (pre-Node 20, some older polyfills). This matches the existing convention in worker-utils.ts:57 and prod-server.ts:173, so it's consistent — just noting it's fine as-is.
|
Approved the PR. Here's a summary of the review: Verdict: APPROVE The fix is correct and well-scoped.
Tests cover multiple cookies, zero cookies, and a full store/restore round-trip. |
Summary
buildAppRouteCacheValue()usedheaders.forEach()which overwrites same-key entries, dropping all but the lastSet-Cookieheader from cached responsesbuildRouteHandlerCachedResponse) already handlesstring[]correctly — only the store path was brokengetSetCookie()to preserve all Set-Cookie headers as astring[], matching the pattern inworker-utils.tsandprod-server.tsTest plan