Repository: sveltejs/kit (js)

Issue:
Cookies are not set after subsequent page reloads
### Describe the bug

Initially discovered by @shahriar350 in #7055

The `handle` and `load` functions do not set a new cookie if it is deleted in the browser.

This is proven by deleting the cookie and reloading the page. The cookies are not set after subsequent reloads.

However, invalidating the page and reloading causes the cookies to be set successfully.

Unfortunately, when running `npm run preview`, only the `handle` cookie gets set this way. The `load` cookie does not get set even after invalidating.

### Reproduction

https://github.com/s3812497/sveltekit-cookie-bug

1. Run the dev server.
2. Open the project in the browser.
3. Delete the cookies.
4. Reload the page.

Expected:
Cookies are set again.

Actual:
Cookies are not set.

### Logs

_No response_

### System Info

```shell
System:
    OS: macOS 13.0
    CPU: (8) arm64 Apple M1 Pro
    Memory: 793.89 MB / 32.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.17.0 - ~/.nvm/versions/node/v16.17.0/bin/node
    npm: 8.15.0 - ~/.nvm/versions/node/v16.17.0/bin/npm
  Browsers:
    Chrome: 105.0.5195.125
    Firefox: 105.0.1
    Firefox Developer Edition: 105.0
    Safari: 16.1
    Safari Technology Preview: 16.0
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.80 
    @sveltejs/kit: next => 1.0.0-next.504 
    svelte: ^3.50.1 => 3.50.1 
    vite: ^3.1.4 => 3.1.4
```


### Severity

serious, but I can work around it

### Additional Information

_No response_

Pull request:
[fix] forward cookie headers on etag response
Fixes #7066

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/gorgeous-months-shave.md b/.changeset/gorgeous-months-shave.md
new file mode 100644
index 000000000..7690c201a
--- /dev/null
+++ b/.changeset/gorgeous-months-shave.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] forward cookie headers on etag response
diff --git a/documentation/docs/02-web-standards.md b/documentation/docs/02-web-standards.md
index 937f1e65d..4f09c7765 100644
--- a/documentation/docs/02-web-standards.md
+++ b/documentation/docs/02-web-standards.md
@@ -45,6 +45,29 @@ export function GET(event) {
 }
 ```
 
+### FormData
+
+When dealing with HTML native form submissions you'll be working with [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) objects.
+
+```js
+// @errors: 2461
+/// file: src/routes/hello/+server.js
+import { json } from '@sveltejs/kit';
+
+/** @type {import('./$types').RequestHandler} */
+export async function POST(event) {
+	const body = await event.request.formData();
+
+	// log all fields
+	console.log([...body]);
+
+	return json({
+		// get a specific field's value
+		name: body.get('name') ?? 'world'
+	});
+}
+```
+
 ### Stream APIs
 
 Most of the time, your endpoints will return complete data, as in the `userAgent` example above. Sometimes, you may need to return a response that's too large to fit in memory in one go, or is delivered in chunks, and for this the platform provides [streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) — [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), [WritableStream](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) and [TransformStream](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream).
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 8c1ec9d1f..ff51bf21d 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -327,8 +327,15 @@ export async function respond(request, options, state) {
 			if (if_none_match_value === etag) {
 				const headers = new Headers({ etag });
 
-				// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
-				for (const key of ['cache-control', 'content-location', 'date', 'expires', 'vary']) {
+				// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
+				for (const key of [
+					'cache-control',
+					'content-location',
+					'date',
+					'expires',
+					'vary',
+					'set-cookie'
+				]) {
 					const value = response.headers.get(key);
 					if (value) headers.set(key, value);
 				}


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/cookies/forwarded-in-etag/+page.server.js b/packages/kit/test/apps/basics/src/routes/cookies/forwarded-in-etag/+page.server.js
new file mode 100644
index 000000000..689539c43
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/cookies/forwarded-in-etag/+page.server.js
@@ -0,0 +1,4 @@
+/** @type {import('./$types').PageServerLoad} */
+export function load({ cookies }) {
+	cookies.set('foo', 'bar', { httpOnly: false });
+}
diff --git a/packages/kit/test/apps/basics/src/routes/cookies/forwarded-in-etag/+page.svelte b/packages/kit/test/apps/basics/src/routes/cookies/forwarded-in-etag/+page.svelte
new file mode 100644
index 000000000..64ec825cd
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/cookies/forwarded-in-etag/+page.svelte
@@ -0,0 +1,20 @@
+<script>
+	import { onMount } from 'svelte';
+
+	let cookies;
+
+	onMount(() => {
+		cookies = document.cookie;
+	});
+</script>
+
+<button
+	on:click={() => {
+		document.cookie = 'foo=bar;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
+		location.reload();
+	}}
+>
+	Delete cookies and reload the page
+</button>
+
+<p>{cookies}</p>
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index b847d6dfd..1e34c648f 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -930,3 +930,12 @@ test.describe('Content negotiation', () => {
 		await expect(page.locator('[data-testid="form-result"]')).toHaveText('form.submitted: true');
 	});
 });
+
+test.describe('cookies', () => {
+	test('etag forwards cookies', async ({ page }) => {
+		await page.goto('/cookies/forwarded-in-etag');
+		await expect(page.locator('p')).toHaveText('foo=bar');
+		await Promise.all([page.waitForNavigation(), page.click('button')]);
+		await expect(page.locator('p')).toHaveText('foo=bar');
+	});
+});