Closed
Description
I'm using React Router as a...
framework
Description
When using React Router v7 (framework mode) on Node.js 18+ with native fetch and no polyfills, only the last Set-Cookie
header is sent to the client, even though multiple are appended in the action/loader.
--
Steps to Reproduce
- In an action, append two cookies:
const headers = new Headers(); headers.append('Set-Cookie', 'cookie1=foo; Path=/; SameSite=Lax'); headers.append('Set-Cookie', 'cookie2=bar; Path=/; SameSite=Lax'); return new Response(null, { status: 204, headers });
- In
server.js
, use:import { createRequestListener } from '@react-router/node'; app.all('*', createRequestListener({ build, mode }));
- Trigger the action from the browser.
- Inspect the response headers in the browser’s network tab.
What I Already Tried (and Did NOT Work)
- Node.js 22+ (undici/native fetch)
- No fetch polyfills (
node-fetch
,cross-fetch
, etc.) anywhere in the codebase. - installGlobals({ nativeFetch: true }) at the top of the server entry (tried both with and without this, no difference).
- Correct middleware usage:
app.all('*name', createRequestListener({ build, mode }));
- Checked the React Router source code and saw it uses
getSetCookie()
if available. - Checked the browser network tab:
Only one Set-Cookie header is present in the response. - Tried making a custom request handler in Express to manually forward all Set-Cookie headers:
- Attempted to call
createRequestListener
and await a Response object to extract headers. - Attempted to patch
res.setHeader
andres.end
to log and forward all Set-Cookie headers. - Attempted to use
response.headers.getSetCookie()
and fallback to iterating over headers. - All custom handler attempts failed because
createRequestListener
does not return a Response object (it is middleware), and/or only the last Set-Cookie header is ever available. - Any attempt to intercept the Response object in userland results in
undefined
errors or only the last cookie being sent.
- Attempted to call
Where Exactly It Goes Wrong
- The React Router server runtime code (headers.ts#L101) is supposed to use
getSetCookie()
to forward all cookies if available. - In practice, even with undici/native fetch and all conditions met, only the last Set-Cookie header is sent to the client.
- My logs show both cookies are appended in the action, but only one is present in the actual HTTP response.
- This suggests the bug is in how the framework serializes or forwards the headers from the Response object to the actual HTTP response.
- Custom request handler attempts in userland cannot fix this, as the Response object is not exposed.
Request
Please investigate why only one Set-Cookie header is sent. If there is a workaround or a recommended pattern for this use case, then I would like to learn about this.
Environment
- React Router version:
- @react-router/node version:
- Node.js version: 22.14.0
- Express version:
- OS: macOS (Darwin 24.2.0)
- No fetch polyfills (node-fetch, cross-fetch, etc.) in use
- Using undici/native fetch (Node 18+)
System Info
System:
OS: macOS 15.2
CPU: (10) arm64 Apple M1 Pro
Memory: 85.64 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.14.0 - ~/.nvm/versions/node/v22.14.0/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 10.9.2 - ~/.nvm/versions/node/v22.14.0/bin/npm
Browsers:
Chrome: 136.0.7103.114
Safari: 18.2
npmPackages:
@react-router/dev: ^7.2.0 => 7.4.0
@react-router/node: ^7.2.0 => 7.4.0
@react-router/serve: ^7.2.0 => 7.4.0
react-router: ^7.2.0 => 7.4.0
vite: ^6.0.0 => 6.2.2
Used Package Manager
npm
Expected Behavior
Both cookies should be present in the response headers:
Set-Cookie: cookie1=foo; Path=/; SameSite=Lax
Set-Cookie: cookie2=bar; Path=/; SameSite=Lax
Actual Behavior
Only the last cookie is present:
Set-Cookie: cookie2=bar; Path=/; SameSite=Lax