From c9168d0bfbc994c71492e18bd32eaf18af980e7a Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Fri, 27 Jun 2025 12:07:08 -0500 Subject: [PATCH 01/12] Add Cookie Error When making a request via the event.fetch to the same exact domain the svelte app is running on, it fails returning a 500 due to the header being immutable. This only happens when a cookie is set in the hooks before trying to make a request with the event.fetch --- packages/kit/src/runtime/server/cookie.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index 2e683543a534..afcf3ff5f8a6 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -274,15 +274,19 @@ export function path_matches(path, constraint) { */ export function add_cookies_to_headers(headers, cookies) { for (const new_cookie of cookies) { - const { name, value, options } = new_cookie; - headers.append('set-cookie', serialize(name, value, options)); - - // special case — for routes ending with .html, the route data lives in a sibling - // `.html__data.json` file rather than a child `/__data.json` file, which means - // we need to duplicate the cookie - if (options.path.endsWith('.html')) { - const path = add_data_suffix(options.path); - headers.append('set-cookie', serialize(name, value, { ...options, path })); + try { + const { name, value, options } = new_cookie; + headers.append('set-cookie', serialize(name, value, options)); + + // special case — for routes ending with .html, the route data lives in a sibling + // `.html__data.json` file rather than a child `/__data.json` file, which means + // we need to duplicate the cookie + if (options.path.endsWith('.html')) { + const path = add_data_suffix(options.path); + headers.append('set-cookie', serialize(name, value, { ...options, path })); + } + } catch (error) { + console.error(`Failed to add cookie "${new_cookie.name}":`, error); } } } From 70b133eb04bf8240496d18081fcc9e23b6eec809 Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Fri, 27 Jun 2025 12:42:21 -0500 Subject: [PATCH 02/12] Add Changeset --- .changeset/breezy-poets-grow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/breezy-poets-grow.md diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md new file mode 100644 index 000000000000..034a144d8da5 --- /dev/null +++ b/.changeset/breezy-poets-grow.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Ignores errors for cookies being added to headers that may be immutable do to the cookies being added prior. From fec11ed8490edbeef2d1ac90d24d5173ec9786e9 Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Mon, 30 Jun 2025 15:28:43 -0500 Subject: [PATCH 03/12] Update .changeset/breezy-poets-grow.md Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .changeset/breezy-poets-grow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md index 034a144d8da5..1ffe8e286bc0 100644 --- a/.changeset/breezy-poets-grow.md +++ b/.changeset/breezy-poets-grow.md @@ -2,4 +2,4 @@ '@sveltejs/kit': patch --- -Ignores errors for cookies being added to headers that may be immutable do to the cookies being added prior. +fix: continue if error encountered when cookie is being added to immutable headers due to the cookies already being added From 2f179d947d6166b7d4594d9a638a8f752fb74b77 Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Mon, 30 Jun 2025 17:34:24 -0500 Subject: [PATCH 04/12] Sub Requests have immutable headers From my testing, all sub requests have immutable errors and will fail if cookies are added to them. This will add a check to skip adding the cookies if it is a sub request. --- .changeset/breezy-poets-grow.md | 2 +- packages/kit/src/runtime/server/cookie.js | 22 +++++++++------------- packages/kit/src/runtime/server/respond.js | 4 +++- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md index 1ffe8e286bc0..4eb661aff8ea 100644 --- a/.changeset/breezy-poets-grow.md +++ b/.changeset/breezy-poets-grow.md @@ -2,4 +2,4 @@ '@sveltejs/kit': patch --- -fix: continue if error encountered when cookie is being added to immutable headers due to the cookies already being added +fix: add a check to prevent adding new cookies to sub requests diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js index afcf3ff5f8a6..2e683543a534 100644 --- a/packages/kit/src/runtime/server/cookie.js +++ b/packages/kit/src/runtime/server/cookie.js @@ -274,19 +274,15 @@ export function path_matches(path, constraint) { */ export function add_cookies_to_headers(headers, cookies) { for (const new_cookie of cookies) { - try { - const { name, value, options } = new_cookie; - headers.append('set-cookie', serialize(name, value, options)); - - // special case — for routes ending with .html, the route data lives in a sibling - // `.html__data.json` file rather than a child `/__data.json` file, which means - // we need to duplicate the cookie - if (options.path.endsWith('.html')) { - const path = add_data_suffix(options.path); - headers.append('set-cookie', serialize(name, value, { ...options, path })); - } - } catch (error) { - console.error(`Failed to add cookie "${new_cookie.name}":`, error); + const { name, value, options } = new_cookie; + headers.append('set-cookie', serialize(name, value, options)); + + // special case — for routes ending with .html, the route data lives in a sibling + // `.html__data.json` file rather than a child `/__data.json` file, which means + // we need to duplicate the cookie + if (options.path.endsWith('.html')) { + const path = add_data_suffix(options.path); + headers.append('set-cookie', serialize(name, value, { ...options, path })); } } } diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 4ca265beab88..710ec2fa6f6c 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -377,7 +377,9 @@ export async function respond(request, options, manifest, state) { response.headers.set(key, /** @type {string} */ (value)); } - add_cookies_to_headers(response.headers, Object.values(new_cookies)); + if (!event.isSubRequest) { + add_cookies_to_headers(response.headers, Object.values(new_cookies)); + } if (state.prerendering && event.route.id !== null) { response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id)); From 75a1051837c8b8162f654f781a498ed5dc44da88 Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Tue, 1 Jul 2025 09:14:35 -0500 Subject: [PATCH 05/12] New Response Object Use a new response object to avoid the headers being immutable when adding new cookies. --- .changeset/breezy-poets-grow.md | 2 +- packages/kit/src/runtime/server/respond.js | 23 +++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md index 4eb661aff8ea..83f4bc6fd4af 100644 --- a/.changeset/breezy-poets-grow.md +++ b/.changeset/breezy-poets-grow.md @@ -2,4 +2,4 @@ '@sveltejs/kit': patch --- -fix: add a check to prevent adding new cookies to sub requests +fix: prevent immutable headers by using a new response object diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 710ec2fa6f6c..ef3d0325fd60 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -370,16 +370,21 @@ export async function respond(request, options, manifest, state) { // e.g. accessible when loading modules needed to handle the request with_event(null, () => resolve(event, page_nodes, opts).then((response) => { - // add headers/cookies here, rather than inside `resolve`, so that we - // can do it once for all responses instead of once per `return` - for (const key in headers) { - const value = headers[key]; - response.headers.set(key, /** @type {string} */ (value)); - } + const responseHeaders = response.headers; - if (!event.isSubRequest) { - add_cookies_to_headers(response.headers, Object.values(new_cookies)); - } + response = new Response(response.body, { + headers, + status: response.status, + statusText: response.statusText + }); + + responseHeaders.forEach((value, key) => { + if (!response.headers.has(key)){ + response.headers.set(key, value); + } + }); + + add_cookies_to_headers(response.headers, Object.values(new_cookies)); if (state.prerendering && event.route.id !== null) { response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id)); From b5e62cef080947834027944686a6b29764cb47ab Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Tue, 1 Jul 2025 09:15:26 -0500 Subject: [PATCH 06/12] Format --- packages/kit/src/runtime/server/respond.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index ef3d0325fd60..2b70efefaa45 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -379,7 +379,7 @@ export async function respond(request, options, manifest, state) { }); responseHeaders.forEach((value, key) => { - if (!response.headers.has(key)){ + if (!response.headers.has(key)) { response.headers.set(key, value); } }); From 284741c6bc6fbea74e1e0466f0bc6cab4b90604b Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Tue, 1 Jul 2025 09:24:24 -0500 Subject: [PATCH 07/12] New Response Object v2 Looked more at other code and found another place where a new response object is created. I based these changes on that to limit the amount of code being changed. --- packages/kit/src/runtime/server/respond.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 2b70efefaa45..1642bcebde55 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -370,19 +370,18 @@ export async function respond(request, options, manifest, state) { // e.g. accessible when loading modules needed to handle the request with_event(null, () => resolve(event, page_nodes, opts).then((response) => { - const responseHeaders = response.headers; - response = new Response(response.body, { - headers, status: response.status, - statusText: response.statusText + statusText: response.statusText, + headers: new Headers(response.headers) }); - responseHeaders.forEach((value, key) => { - if (!response.headers.has(key)) { - response.headers.set(key, value); - } - }); + // add headers/cookies here, rather than inside `resolve`, so that we + // can do it once for all responses instead of once per `return` + for (const key in headers) { + const value = headers[key]; + response.headers.set(key, /** @type {string} */ (value)); + } add_cookies_to_headers(response.headers, Object.values(new_cookies)); From da51d2b136fa619512059333700512ef0aa9b778 Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Tue, 1 Jul 2025 11:37:23 -0500 Subject: [PATCH 08/12] Update .changeset/breezy-poets-grow.md Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .changeset/breezy-poets-grow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md index 83f4bc6fd4af..2bba3296fdd9 100644 --- a/.changeset/breezy-poets-grow.md +++ b/.changeset/breezy-poets-grow.md @@ -2,4 +2,4 @@ '@sveltejs/kit': patch --- -fix: prevent immutable headers by using a new response object +fix: prevent error attempting to modify immutable headers by creating a new `Response` object From 919e8ab4e233fe73bf86899f1cbd7a571b06cf8a Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Tue, 1 Jul 2025 11:56:55 -0500 Subject: [PATCH 09/12] Only create copy if it is from fetch --- .changeset/breezy-poets-grow.md | 2 +- packages/kit/src/runtime/server/respond.js | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md index 2bba3296fdd9..949ca16b2931 100644 --- a/.changeset/breezy-poets-grow.md +++ b/.changeset/breezy-poets-grow.md @@ -2,4 +2,4 @@ '@sveltejs/kit': patch --- -fix: prevent error attempting to modify immutable headers by creating a new `Response` object +fix: prevent error attempting to modify immutable headers by creating a new `Response` object from the fetch response diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 1642bcebde55..19cdafbb004e 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -370,12 +370,6 @@ export async function respond(request, options, manifest, state) { // e.g. accessible when loading modules needed to handle the request with_event(null, () => resolve(event, page_nodes, opts).then((response) => { - response = new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: new Headers(response.headers) - }); - // add headers/cookies here, rather than inside `resolve`, so that we // can do it once for all responses instead of once per `return` for (const key in headers) { @@ -613,7 +607,14 @@ export async function respond(request, options, manifest, state) { // we can't load the endpoint from our own manifest, // so we need to make an actual HTTP request - return await fetch(request); + const fetchResponse = await fetch(request); + + // the header for the response needs to be mutable, so we need to clone it + return new Response(fetchResponse.body, { + status: fetchResponse.status, + statusText: fetchResponse.statusText, + headers: new Headers(fetchResponse.headers) + }); } catch (e) { // TODO if `e` is instead named `error`, some fucked up Vite transformation happens // and I don't even know how to describe it. need to investigate at some point From 2544c8c5e8dba1352c1c7935843dc66f98c9e8f9 Mon Sep 17 00:00:00 2001 From: Jacob Lewinski Date: Mon, 4 Aug 2025 12:13:45 -0500 Subject: [PATCH 10/12] Update packages/kit/src/runtime/server/respond.js --- packages/kit/src/runtime/server/respond.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 19cdafbb004e..226d6e0175bd 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -609,7 +609,7 @@ export async function respond(request, options, manifest, state) { // so we need to make an actual HTTP request const fetchResponse = await fetch(request); - // the header for the response needs to be mutable, so we need to clone it + // the headers for the response needs to be mutable so that cookies can be added return new Response(fetchResponse.body, { status: fetchResponse.status, statusText: fetchResponse.statusText, From f446cc04d0529df75df08e7d95afddca511d4232 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Aug 2025 15:00:50 -0400 Subject: [PATCH 11/12] tweak --- packages/kit/src/runtime/server/respond.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 6caf199a96ea..924c63186869 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -682,14 +682,10 @@ export async function internal_respond(request, options, manifest, state) { // we can't load the endpoint from our own manifest, // so we need to make an actual HTTP request - const fetchResponse = await fetch(request); + const response = await fetch(request); - // the headers for the response needs to be mutable so that cookies can be added - return new Response(fetchResponse.body, { - status: fetchResponse.status, - statusText: fetchResponse.statusText, - headers: new Headers(fetchResponse.headers) - }); + // clone the response so that headers are mutable (https://github.com/sveltejs/kit/issues/13857) + return new Response(response.body, response); } catch (e) { // TODO if `e` is instead named `error`, some fucked up Vite transformation happens // and I don't even know how to describe it. need to investigate at some point From dbd32bff0ca06231f253d9d09e8024a723f3bbcb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Aug 2025 15:02:11 -0400 Subject: [PATCH 12/12] Update .changeset/breezy-poets-grow.md --- .changeset/breezy-poets-grow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/breezy-poets-grow.md b/.changeset/breezy-poets-grow.md index 949ca16b2931..06135d23f501 100644 --- a/.changeset/breezy-poets-grow.md +++ b/.changeset/breezy-poets-grow.md @@ -2,4 +2,4 @@ '@sveltejs/kit': patch --- -fix: prevent error attempting to modify immutable headers by creating a new `Response` object from the fetch response +fix: clone `fetch` responses so that headers are mutable