Skip to content

fix: strip Set-Cookie from App Route ISR cache entries#818

Open
southpolesteve wants to merge 1 commit intomainfrom
fix/strip-set-cookie-from-app-route-isr-cache-clean
Open

fix: strip Set-Cookie from App Route ISR cache entries#818
southpolesteve wants to merge 1 commit intomainfrom
fix/strip-set-cookie-from-app-route-isr-cache-clean

Conversation

@southpolesteve
Copy link
Copy Markdown
Collaborator

Summary

App Route ISR cache values no longer persist Set-Cookie headers.

Details

Cached App Route responses are shared across requests. Persisting Set-Cookie inside the ISR cache would replay per-request cookies to later visitors on HIT/STALE responses.

This change updates buildAppRouteCacheValue() to keep dropping:

  • set-cookie
  • x-vinext-cache
  • cache-control

Tests

Adds a dedicated cacheable route handler fixture (/api/static-set-cookie) with revalidate = 1 that sets Set-Cookie directly on the response.

Regression coverage verifies:

  • first request: MISS and Set-Cookie present
  • second request: HIT and Set-Cookie absent
  • body is still cached correctly across the HIT

App Route ISR cache values were persisting Set-Cookie headers from
cacheable GET route handlers and replaying them on later HIT/STALE
responses. Cached route handler responses are shared across requests,
so Set-Cookie must never be stored in the route ISR cache.

Fix:
- drop Set-Cookie entirely in buildAppRouteCacheValue()
- keep X-Vinext-Cache and Cache-Control excluded as before
- add a dedicated static route handler fixture with revalidate=1 that
  sets Set-Cookie directly on the response
- add a regression test proving the MISS response includes Set-Cookie
  but the cached HIT response does not
@southpolesteve
Copy link
Copy Markdown
Collaborator Author

/bigbonk review this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 10, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@818

commit: c88f837

@github-actions
Copy link
Copy Markdown

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

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Clean, correct fix. The bug was that buildAppRouteCacheValue() filtered set-cookie in the forEach loop but then immediately re-added it via the getSetCookie() block below. Removing that block is the right fix.

A few observations from tracing the call sites:

The cookies().set() API path was never affected. In app-route-handler-execution.ts:145-172, the clone is taken from the raw handler response before finalizeRouteHandlerResponse() adds pending cookies from cookies().set(). So only direct Set-Cookie headers on the Response constructor (like the new fixture) would have been cached. The cookies() API cookies were safe by accident of ordering. Good to know that this PR closes the remaining gap.

STALE regen path is also covered. The background regeneration in app-route-handler-cache.ts:116 calls buildAppRouteCacheValue(response) on a freshly-executed handler response, so the same filter applies there.

Test placement is reasonable given the order-dependent ISR test block. The new test creates its own cache key (/api/static-set-cookie) so it doesn't interfere with the /api/static-data tests around it.

LGTM.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 10, 2026

Reviewed and approved PR #818.

The fix is correct: the old code had a contradictory pattern where the forEach loop filtered out set-cookie, but a subsequent getSetCookie() block re-added it to the cache value. Since ISR cache entries are shared across requests, this would replay one user's Set-Cookie header to all subsequent visitors on HIT/STALE responses. Removing the getSetCookie() block lets the existing filter do its job.

Key findings from the review:

  • The cookies().set() API path was never affected — pending cookies are added by finalizeRouteHandlerResponse() after the clone is taken for caching, so they were safe by accident of ordering.
  • The STALE background regeneration path in app-route-handler-cache.ts also benefits from this fix since it calls the same buildAppRouteCacheValue().
  • The test is well-designed with its own cache key (/api/static-set-cookie) so it doesn't interfere with the adjacent order-dependent ISR tests.

github run

@james-elicx
Copy link
Copy Markdown
Collaborator

I think this might be a deviation from Next.js, albeit arguably a correct one.

https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/app-route.ts#L369-L398

@southpolesteve
Copy link
Copy Markdown
Collaborator Author

Yeah good catch @james-elicx. I'm honestly torn about how to proceed here.

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.

2 participants