[PR #955] [MERGED] fix(app-routes): preserve returned cookie precedence #983

Closed
opened 2026-05-06 13:11:23 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/955
Author: @NathanDrake2406
Created: 4/29/2026
Status: Merged
Merged: 4/29/2026
Merged by: @james-elicx

Base: mainHead: nathan/app-route-cookie-precedence


📝 Commits (1)

  • f00eefe fix(app-routes): preserve returned cookie precedence

📊 Changes

4 files changed (+107 additions, -3 deletions)

View changed files

📝 packages/vinext/src/server/app-route-handler-response.ts (+49 -3)
📝 tests/app-route-handler-response.test.ts (+29 -0)
tests/fixtures/app-basic/app/nextjs-compat/api/cookie-precedence/route.ts (+14 -0)
📝 tests/nextjs-compat/set-cookies.test.ts (+15 -0)

📄 Description

What this changes

App Route handler finalization now treats cookies produced by cookies().set() as fallback Set-Cookie values. Cookies already present on the returned Response are re-applied last, so same-name returned response cookies remain the final values.

Why

vinext previously cloned the returned response headers and appended pending mutable cookies afterward. When a route both mutates cookies() and returns a Response or NextResponse with a cookie of the same name, the mutable cookie appeared later in Set-Cookie order and could override the returned response cookie.

Next.js does the opposite. Its appendMutableCookies helper records returned response cookies, sets mutable cookies as fallbacks, then sets the original returned cookies as final values: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L72-L98

The App Route module calls that helper after a handler returns a Response: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/route-modules/app-route/module.ts#L783-L792

Approach

The finalizer now extracts returned Set-Cookie names, deduplicates pending mutable cookies by name so the last mutation wins, skips mutable cookies shadowed by returned response cookies, and then writes fallbacks before the returned response cookies. This mirrors Next.js without normalizing cookie attributes or changing unrelated header behavior.

Validation

  • vp test run tests/app-route-handler-response.test.ts tests/nextjs-compat/set-cookies.test.ts
  • vp check packages/vinext/src/server/app-route-handler-response.ts tests/app-route-handler-response.test.ts tests/nextjs-compat/set-cookies.test.ts tests/fixtures/app-basic/app/nextjs-compat/api/cookie-precedence/route.ts

Risks / follow-ups

This is scoped to App Route handler response finalization. Server Action redirect cookie paths already have no returned response cookie to merge against, so they are intentionally outside this change.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/955 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 4/29/2026 **Status:** ✅ Merged **Merged:** 4/29/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `nathan/app-route-cookie-precedence` --- ### 📝 Commits (1) - [`f00eefe`](https://github.com/cloudflare/vinext/commit/f00eefe649cc517ea3fb8264e3971b431bcc8f43) fix(app-routes): preserve returned cookie precedence ### 📊 Changes **4 files changed** (+107 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/server/app-route-handler-response.ts` (+49 -3) 📝 `tests/app-route-handler-response.test.ts` (+29 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/api/cookie-precedence/route.ts` (+14 -0) 📝 `tests/nextjs-compat/set-cookies.test.ts` (+15 -0) </details> ### 📄 Description ## What this changes App Route handler finalization now treats cookies produced by `cookies().set()` as fallback `Set-Cookie` values. Cookies already present on the returned `Response` are re-applied last, so same-name returned response cookies remain the final values. ## Why vinext previously cloned the returned response headers and appended pending mutable cookies afterward. When a route both mutates `cookies()` and returns a `Response` or `NextResponse` with a cookie of the same name, the mutable cookie appeared later in `Set-Cookie` order and could override the returned response cookie. Next.js does the opposite. Its `appendMutableCookies` helper records returned response cookies, sets mutable cookies as fallbacks, then sets the original returned cookies as final values: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L72-L98 The App Route module calls that helper after a handler returns a `Response`: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/route-modules/app-route/module.ts#L783-L792 ## Approach The finalizer now extracts returned `Set-Cookie` names, deduplicates pending mutable cookies by name so the last mutation wins, skips mutable cookies shadowed by returned response cookies, and then writes fallbacks before the returned response cookies. This mirrors Next.js without normalizing cookie attributes or changing unrelated header behavior. ## Validation - `vp test run tests/app-route-handler-response.test.ts tests/nextjs-compat/set-cookies.test.ts` - `vp check packages/vinext/src/server/app-route-handler-response.ts tests/app-route-handler-response.test.ts tests/nextjs-compat/set-cookies.test.ts tests/fixtures/app-basic/app/nextjs-compat/api/cookie-precedence/route.ts` ## Risks / follow-ups This is scoped to App Route handler response finalization. Server Action redirect cookie paths already have no returned response cookie to merge against, so they are intentionally outside this change. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:23 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#983
No description provided.