Repository: sveltejs/kit
Pull request #6828 (2022-09-15 11:47:12)

[fix] apply redirect/error logic for use:enhance
Came up in some issue that was closed but a comment in there made this good point that this isn't working as expected.

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [ ] 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


changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 3fd5728825aa24734af1bd38421e9693ce49d88d

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name          | Type  |
| ------------- | ----- |
| @sveltejs/kit | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/sveltejs/kit/new/enhance-redirect?filename=.changeset/wild-singers-fold.md&value=---%0A%22%40sveltejs%2Fkit%22%3A%20patch%0A%22test-basics%22%3A%20patch%0A---%0A%0A%5Bfix%5D%20apply%20redirect%2Ferror%20logic%20for%20use%3Aenhance%0A)



Patch:
diff --git a/.changeset/afraid-gifts-act.md b/.changeset/afraid-gifts-act.md
new file mode 100644
index 000000000..7f4a0c421
--- /dev/null
+++ b/.changeset/afraid-gifts-act.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/adapter-cloudflare': patch
+'@sveltejs/kit': patch
+---
+
+Support Cloudflare Pages `_routes.json` specification
diff --git a/.changeset/cool-beans-only.md b/.changeset/cool-beans-only.md
new file mode 100644
index 000000000..755343640
--- /dev/null
+++ b/.changeset/cool-beans-only.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": patch
+---
+
+fix `write_types` on windows using posixify()
diff --git a/.changeset/cuddly-dragons-punch.md b/.changeset/cuddly-dragons-punch.md
new file mode 100644
index 000000000..1491e81fb
--- /dev/null
+++ b/.changeset/cuddly-dragons-punch.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Fix fetch type
diff --git a/.changeset/cyan-years-live.md b/.changeset/cyan-years-live.md
new file mode 100644
index 000000000..0ed5696bb
--- /dev/null
+++ b/.changeset/cyan-years-live.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Prevent caching of `__data.js` files
diff --git a/.changeset/dirty-candles-peel.md b/.changeset/dirty-candles-peel.md
new file mode 100644
index 000000000..5ea2f31ae
--- /dev/null
+++ b/.changeset/dirty-candles-peel.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] allow missing routes folder
diff --git a/.changeset/dry-snails-remain.md b/.changeset/dry-snails-remain.md
new file mode 100644
index 000000000..448501302
--- /dev/null
+++ b/.changeset/dry-snails-remain.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] add missing depends function to ServerLoadEvent type
diff --git a/.changeset/dull-cows-sneeze.md b/.changeset/dull-cows-sneeze.md
new file mode 100644
index 000000000..fd084fe7d
--- /dev/null
+++ b/.changeset/dull-cows-sneeze.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/adapter-cloudflare': patch
+'@sveltejs/adapter-cloudflare-workers': patch
+---
+
+Include ambient.d.ts files in adapter packages.
diff --git a/.changeset/early-olives-burn.md b/.changeset/early-olives-burn.md
new file mode 100644
index 000000000..ae81419bf
--- /dev/null
+++ b/.changeset/early-olives-burn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': patch
+---
+
+Add missing Rollup dependency
diff --git a/.changeset/fifty-suns-hope.md b/.changeset/fifty-suns-hope.md
new file mode 100644
index 000000000..c261c2b40
--- /dev/null
+++ b/.changeset/fifty-suns-hope.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] update page status when applying action
diff --git a/.changeset/five-tools-arrive.md b/.changeset/five-tools-arrive.md
new file mode 100644
index 000000000..139d51a0e
--- /dev/null
+++ b/.changeset/five-tools-arrive.md
@@ -0,0 +1,7 @@
+---
+"@sveltejs/adapter-node": patch
+"@sveltejs/adapter-vercel": patch
+"@sveltejs/kit": patch
+---
+
+Redact error message if `getRequest` fails
diff --git a/.changeset/flat-owls-care.md b/.changeset/flat-owls-care.md
new file mode 100644
index 000000000..484be96f9
--- /dev/null
+++ b/.changeset/flat-owls-care.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Add cookies.serialize method
diff --git a/.changeset/great-tables-learn.md b/.changeset/great-tables-learn.md
new file mode 100644
index 000000000..8d6d60440
--- /dev/null
+++ b/.changeset/great-tables-learn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Run `svelte-kit sync` in all workspace directories during postinstall
diff --git a/.changeset/healthy-rivers-share.md b/.changeset/healthy-rivers-share.md
new file mode 100644
index 000000000..8aac9d659
--- /dev/null
+++ b/.changeset/healthy-rivers-share.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/adapter-cloudflare": patch
+---
+
+[fix] return 404 instead of 200 for missing assets
diff --git a/.changeset/lazy-mice-remain.md b/.changeset/lazy-mice-remain.md
new file mode 100644
index 000000000..519d0d7fd
--- /dev/null
+++ b/.changeset/lazy-mice-remain.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[feat] allow +server.js files next to +page files
diff --git a/.changeset/mean-chefs-swim.md b/.changeset/mean-chefs-swim.md
new file mode 100644
index 000000000..f37560970
--- /dev/null
+++ b/.changeset/mean-chefs-swim.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Add csp wasm-unsafe-eval keyword
diff --git a/.changeset/odd-beds-smile.md b/.changeset/odd-beds-smile.md
new file mode 100644
index 000000000..63aa75272
--- /dev/null
+++ b/.changeset/odd-beds-smile.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': patch
+---
+
+Bundle with rollup instead of esbuild
diff --git a/.changeset/orange-cups-itch.md b/.changeset/orange-cups-itch.md
new file mode 100644
index 000000000..7a0b57312
--- /dev/null
+++ b/.changeset/orange-cups-itch.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Register service worker with relative URL
diff --git a/.changeset/orange-rocks-return.md b/.changeset/orange-rocks-return.md
new file mode 100644
index 000000000..74075106f
--- /dev/null
+++ b/.changeset/orange-rocks-return.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-cloudflare': patch
+---
+
+Ensure all excluded paths in `_routes.json` start with /
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 5d319f9d5..890c2b14f 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -45,6 +45,7 @@
     "afraid-eels-tease",
     "afraid-flowers-train",
     "afraid-games-jog",
+    "afraid-gifts-act",
     "afraid-kings-mix",
     "afraid-llamas-matter",
     "afraid-oranges-doubt",
@@ -179,6 +180,7 @@
     "cold-shirts-lick",
     "cold-tables-shake",
     "cool-bananas-share",
+    "cool-beans-only",
     "cool-birds-attack",
     "cool-crabs-listen",
     "cool-drinks-press",
@@ -187,6 +189,7 @@
     "cool-spoons-dress",
     "cool-swans-sleep",
     "cuddly-ads-kick",
+    "cuddly-dragons-punch",
     "cuddly-files-decide",
     "cuddly-knives-learn",
     "cuddly-months-sip",
@@ -215,13 +218,17 @@
     "cyan-roses-act",
     "cyan-schools-kiss",
     "cyan-seals-trade",
+    "cyan-years-live",
     "dirty-bikes-join",
+    "dirty-candles-peel",
     "dirty-ligers-protect",
     "dry-baboons-turn",
     "dry-cars-dance",
     "dry-jokes-explain",
+    "dry-snails-remain",
     "dry-yaks-clean",
     "dull-cheetahs-clean",
+    "dull-cows-sneeze",
     "dull-ducks-report",
     "dull-emus-tap",
     "dull-lobsters-act",
@@ -235,6 +242,7 @@
     "early-eagles-boil",
     "early-lemons-collect",
     "early-news-taste",
+    "early-olives-burn",
     "early-pants-unite",
     "early-snakes-peel",
     "early-wasps-obey",
@@ -317,11 +325,13 @@
     "five-poems-pretend",
     "five-seas-end",
     "five-shirts-allow",
+    "five-tools-arrive",
     "five-turtles-rhyme",
     "flat-cameras-build",
     "flat-ducks-impress",
     "flat-eels-hear",
     "flat-insects-worry",
+    "flat-owls-care",
     "flat-parrots-juggle",
     "flat-pears-compare",
     "flat-pillows-think",
@@ -418,6 +428,7 @@
     "great-lies-agree",
     "great-queens-rule",
     "great-seahorses-fry",
+    "great-tables-learn",
     "green-garlics-warn",
     "green-kiwis-roll",
     "green-masks-confess",
@@ -445,6 +456,7 @@
     "healthy-penguins-fail",
     "healthy-pigs-fly",
     "healthy-pumpkins-bathe",
+    "healthy-rivers-share",
     "healthy-vans-build",
     "healthy-wasps-poke",
     "heavy-donuts-grab",
@@ -538,6 +550,7 @@
     "lazy-ants-watch",
     "lazy-berries-decide",
     "lazy-carpets-join",
+    "lazy-mice-remain",
     "lazy-mice-smile",
     "lazy-owls-run",
     "lazy-spies-wash",
@@ -611,6 +624,7 @@
     "many-pugs-work",
     "many-suns-push",
     "mean-bananas-live",
+    "mean-chefs-swim",
     "mean-crews-unite",
     "mean-mangos-marry",
     "mean-pets-confess",
@@ -721,6 +735,7 @@
     "ninety-tables-switch",
     "ninety-vans-talk",
     "ninety-walls-heal",
+    "odd-beds-smile",
     "odd-bottles-drop",
     "odd-coats-shake",
     "odd-coins-lie",
@@ -752,6 +767,8 @@
     "olive-socks-thank",
     "olive-vans-talk",
     "orange-boats-love",
+    "orange-cups-itch",
+    "orange-rocks-return",
     "orange-ties-tell",
     "orange-wolves-fly",
     "perfect-cycles-smell",
@@ -960,6 +977,7 @@
     "short-emus-juggle",
     "short-guests-sort",
     "short-lamps-sing",
+    "short-spies-relax",
     "shy-bats-move",
     "shy-jeans-fly",
     "shy-mails-share",
@@ -967,6 +985,8 @@
     "shy-toes-punch",
     "silent-crews-change",
     "silent-cycles-reply",
+    "silent-jeans-vanish",
+    "silent-wasps-sniff",
     "silly-ants-battle",
     "silly-grapes-cover",
     "silly-jokes-hug",
@@ -979,6 +999,7 @@
     "silver-hounds-clean",
     "silver-lemons-fail",
     "silver-needles-judge",
+    "silver-pugs-worry",
     "silver-rice-flash",
     "silver-scissors-peel",
     "silver-toes-cheer",
@@ -1012,6 +1033,7 @@
     "smart-apes-drum",
     "smart-bears-leave",
     "smart-deers-approve",
+    "smart-forks-double",
     "smart-humans-eat",
     "smart-mugs-chew",
     "smart-nails-work",
@@ -1069,6 +1091,7 @@
     "spotty-phones-love",
     "spotty-ties-love",
     "spotty-timers-fix",
+    "spotty-trainers-switch",
     "spotty-vans-tickle",
     "stale-apples-cry",
     "stale-crabs-carry",
@@ -1085,6 +1108,7 @@
     "strange-kiwis-tell",
     "strange-poems-design",
     "strong-apples-walk",
+    "strong-baboons-travel",
     "strong-carrots-work",
     "strong-cats-wonder",
     "strong-cooks-run",
@@ -1337,6 +1361,7 @@
     "witty-maps-pull",
     "witty-meals-tie",
     "witty-news-dress",
+    "witty-shoes-know",
     "yellow-apricots-stare",
     "yellow-coins-wash",
     "yellow-eagles-carry",
@@ -1360,6 +1385,7 @@
     "young-pumpkins-approve",
     "young-scissors-collect",
     "young-students-chew",
+    "young-students-relate",
     "young-swans-burn",
     "young-ways-fail"
   ]
diff --git a/.changeset/short-spies-relax.md b/.changeset/short-spies-relax.md
new file mode 100644
index 000000000..b358986e8
--- /dev/null
+++ b/.changeset/short-spies-relax.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Always apply cookies, not just for matched routes
diff --git a/.changeset/silent-jeans-vanish.md b/.changeset/silent-jeans-vanish.md
new file mode 100644
index 000000000..44b09d9c1
--- /dev/null
+++ b/.changeset/silent-jeans-vanish.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] symlink routes
diff --git a/.changeset/silent-wasps-sniff.md b/.changeset/silent-wasps-sniff.md
new file mode 100644
index 000000000..f6ec7b038
--- /dev/null
+++ b/.changeset/silent-wasps-sniff.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] tighten up navigation and invalidation logic
diff --git a/.changeset/silver-pugs-worry.md b/.changeset/silver-pugs-worry.md
new file mode 100644
index 000000000..27e98e25a
--- /dev/null
+++ b/.changeset/silver-pugs-worry.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] tighten up handling and documentation around 404
diff --git a/.changeset/smart-forks-double.md b/.changeset/smart-forks-double.md
new file mode 100644
index 000000000..e98f3bbab
--- /dev/null
+++ b/.changeset/smart-forks-double.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/package': patch
+---
+
+fix check for undefined on application/ strip
diff --git a/.changeset/smooth-bears-unite.md b/.changeset/smooth-bears-unite.md
new file mode 100644
index 000000000..8659e746f
--- /dev/null
+++ b/.changeset/smooth-bears-unite.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] apply redirect/error logic for `use:enhance`
diff --git a/.changeset/spotty-trainers-switch.md b/.changeset/spotty-trainers-switch.md
new file mode 100644
index 000000000..2cd773e1d
--- /dev/null
+++ b/.changeset/spotty-trainers-switch.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Decode HTML entities in `href` attributes when crawling
diff --git a/.changeset/strong-baboons-travel.md b/.changeset/strong-baboons-travel.md
new file mode 100644
index 000000000..b25d46176
--- /dev/null
+++ b/.changeset/strong-baboons-travel.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": patch
+---
+
+Respond with 413 if request body is too large
diff --git a/.changeset/witty-shoes-know.md b/.changeset/witty-shoes-know.md
new file mode 100644
index 000000000..045cf5139
--- /dev/null
+++ b/.changeset/witty-shoes-know.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Only forward set-cookie headers for internal fetches
diff --git a/.changeset/young-students-relate.md b/.changeset/young-students-relate.md
new file mode 100644
index 000000000..06b05a32b
--- /dev/null
+++ b/.changeset/young-students-relate.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Make url property getters non-enumerable
diff --git a/.gitignore b/.gitignore
index 77c2b8810..f53c51190 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,4 @@ vite.config.js.timestamp-*
 .turbo
 .vercel
 .test-tmp
-
+symlink-from
diff --git a/documentation/docs/03-routing.md b/documentation/docs/03-routing.md
index ac5598dd7..3ca29c382 100644
--- a/documentation/docs/03-routing.md
+++ b/documentation/docs/03-routing.md
@@ -267,6 +267,57 @@ The first argument to `Response` can be a [`ReadableStream`](https://developer.m
 
 You can use the `error`, `redirect` and `json` methods from `@sveltejs/kit` for convenience (but you don't have to). Note that `throw error(..)` only returns a plain text error response.
 
+#### Receiving data
+
+By exporting `POST`/`PUT`/`PATCH`/`DELETE` handlers, `+server.js` files can be used to create a complete API:
+
+```svelte
+/// file: src/routes/add/+page.svelte
+<script>
+	let a = 0;
+	let b = 0;
+	let total = 0;
+
+	async function add() {
+		const response = await fetch('/api/add', {
+			method: 'POST',
+			body: JSON.stringify({ a, b }),
+			headers: {
+				'content-type': 'application/json'
+			}
+		});
+
+		total = await response.json();
+	}
+</script>
+
+<input type="number" bind:value={a}> +
+<input type="number" bind:value={b}> =
+{total}
+
+<button on:click={add}>Calculate</button>
+```
+
+```js
+/// file: src/routes/api/add/+server.js
+import { json } from '@sveltejs/kit';
+
+/** @type {import('./$types').RequestHandler} */
+export async function POST({ request }) {
+	const { a, b } = await request.json();
+	return json(a + b);
+}
+```
+
+> In general, [form actions](/docs/form-actions) are a better way to submit data from the browser to the server.
+
+#### Content negotiation
+
+`+server.js` files can be placed in the same directory as `+page` files, allowing the same route to be either a page or an API endpoint. To determine which, SvelteKit applies the following rules:
+
+- `PUT`/`PATCH`/`DELETE` requests are always handled by `+server.js` since they do not apply to pages
+- `GET`/`POST` requests are treated as page requests if the `accept` header prioritises `text/html` (in other words, it's a browser page request), else they are handled by `+server.js`
+
 ### $types
 
 Throughout the examples above, we've been importing types from a `$types.d.ts` file. This is a file SvelteKit creates for you in a hidden directory if you're using TypeScript (or JavaScript with JSDoc type annotations) to give you type safety when working with your root files.
diff --git a/documentation/docs/04-advanced-routing.md b/documentation/docs/04-advanced-routing.md
index 05c1c813f..2f9d67abe 100644
--- a/documentation/docs/04-advanced-routing.md
+++ b/documentation/docs/04-advanced-routing.md
@@ -22,7 +22,11 @@ If the number of route segments is unknown, you can use rest syntax — for exam
 }
 ```
 
-This also allows you to render custom 404s. Given these routes...
+> `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#matching).
+
+#### 404 pages
+
+Rest parameters also allow you to render custom 404s. Given these routes...
 
 ```
 src/routes/
@@ -47,7 +51,17 @@ src/routes/
 └ +error.svelte
 ```
 
-> `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#matching).
+```js
+/// file: src/routes/marx-brothers/[...path]/+page.js
+import { error } from '@sveltejs/kit';
+
+/** @type {import('./$types').PageLoad} */
+export function load(event) {
+	throw error(404, 'Not Found');
+}
+```
+
+> If you don't handle 404 cases, they will appear in [`handleError`](/docs/hooks#shared-hooks-handleerror)
 
 ### Matching
 
diff --git a/documentation/docs/06-form-actions.md b/documentation/docs/06-form-actions.md
index fcf1300a8..d5fe40ad9 100644
--- a/documentation/docs/06-form-actions.md
+++ b/documentation/docs/06-form-actions.md
@@ -351,3 +351,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
 	<!-- content -->
 </form>
 ```
+
+### Alternatives
+
+Form actions are the preferred way to send data to the server, since they can be progressively enhanced, but you can also use [`+server.js`](/docs/routing#server) files to expose (for example) a JSON API.
\ No newline at end of file
diff --git a/documentation/docs/07-hooks.md b/documentation/docs/07-hooks.md
index 1b1dd3adb..8ede315c3 100644
--- a/documentation/docs/07-hooks.md
+++ b/documentation/docs/07-hooks.md
@@ -136,7 +136,7 @@ The following can be added to `src/hooks.server.js` _and_ `src/hooks.client.js`:
 If an unexpected error is thrown during loading or rendering, this function will be called with the `error` and the `event`. This allows for two things:
 
 - you can log the error
-- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message: 'Internal Error' }`, becomes the value of `$page.error`. To make this type-safe, you can customize the expected shape by declaring an `App.PageError` interface (which must include `message: string`, to guarantee sensible fallback behavior).
+- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value becomes the value of `$page.error`. It defaults to `{ message: 'Not Found' }` in case of a 404 (you can detect them through `event.routeId` being `null`) and to `{ message: 'Internal Error' }` for everything else. To make this type-safe, you can customize the expected shape by declaring an `App.PageError` interface (which must include `message: string`, to guarantee sensible fallback behavior).
 
 The following code shows an example of typing the error shape as `{ message: string; code: string }` and returning it accordingly from the `handleError` functions:
 
diff --git a/package.json b/package.json
index 817041bcc..6109081fc 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
 		"turbo": "^1.3.4",
 		"typescript": "^4.8.2"
 	},
-	"packageManager": "pnpm@7.12.0",
+	"packageManager": "pnpm@7.12.1",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/adapter-auto/CHANGELOG.md b/packages/adapter-auto/CHANGELOG.md
index 1ab5cf148..ca546206b 100644
--- a/packages/adapter-auto/CHANGELOG.md
+++ b/packages/adapter-auto/CHANGELOG.md
@@ -1,5 +1,43 @@
 # @sveltejs/adapter-auto
 
+## 1.0.0-next.80
+
+### Patch Changes
+
+- Updated dependencies [[`d0e63d44`](https://github.com/sveltejs/kit/commit/d0e63d445e19e2df7a5e7d35f42b2ba1ae5e5745)]:
+  - @sveltejs/adapter-cloudflare@1.0.0-next.38
+
+## 1.0.0-next.79
+
+### Patch Changes
+
+- Updated dependencies [[`ae8225d4`](https://github.com/sveltejs/kit/commit/ae8225d4e547c8bc07d82e516dd49315eddb56c3)]:
+  - @sveltejs/adapter-cloudflare@1.0.0-next.37
+  - @sveltejs/adapter-netlify@1.0.0-next.78
+  - @sveltejs/adapter-vercel@1.0.0-next.77
+
+## 1.0.0-next.78
+
+### Patch Changes
+
+- Updated dependencies [[`737160d8`](https://github.com/sveltejs/kit/commit/737160d88cd96323eeac317ed08b4f0d6d83c482)]:
+  - @sveltejs/adapter-vercel@1.0.0-next.77
+  - @sveltejs/adapter-netlify@1.0.0-next.78
+
+## 1.0.0-next.77
+
+### Patch Changes
+
+- Updated dependencies [[`e40acb56`](https://github.com/sveltejs/kit/commit/e40acb561e32a2ee5bcb6d289ebd1284d7af247b)]:
+  - @sveltejs/adapter-cloudflare@1.0.0-next.36
+
+## 1.0.0-next.76
+
+### Patch Changes
+
+- Updated dependencies [[`ae2e4bf2`](https://github.com/sveltejs/kit/commit/ae2e4bf2c5fe0aeba4cba21d51362837f66d1949)]:
+  - @sveltejs/adapter-cloudflare@1.0.0-next.35
+
 ## 1.0.0-next.75
 
 ### Patch Changes
diff --git a/packages/adapter-auto/package.json b/packages/adapter-auto/package.json
index 2df4e1dcd..859537000 100644
--- a/packages/adapter-auto/package.json
+++ b/packages/adapter-auto/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/adapter-auto",
-	"version": "1.0.0-next.75",
+	"version": "1.0.0-next.80",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/adapter-cloudflare-workers/CHANGELOG.md b/packages/adapter-cloudflare-workers/CHANGELOG.md
index b2322760e..2927372a0 100644
--- a/packages/adapter-cloudflare-workers/CHANGELOG.md
+++ b/packages/adapter-cloudflare-workers/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/adapter-cloudflare-workers
 
+## 1.0.0-next.57
+
+### Patch Changes
+
+- Include ambient.d.ts files in adapter packages. ([#6917](https://github.com/sveltejs/kit/pull/6917))
+
 ## 1.0.0-next.56
 
 ### Patch Changes
diff --git a/packages/adapter-cloudflare-workers/package.json b/packages/adapter-cloudflare-workers/package.json
index f856b8e32..2504210b5 100644
--- a/packages/adapter-cloudflare-workers/package.json
+++ b/packages/adapter-cloudflare-workers/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/adapter-cloudflare-workers",
-	"version": "1.0.0-next.56",
+	"version": "1.0.0-next.57",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -19,6 +19,7 @@
 	"main": "index.js",
 	"types": "index.d.ts",
 	"files": [
+		"ambient.d.ts",
 		"files",
 		"index.d.ts"
 	],
diff --git a/packages/adapter-cloudflare/CHANGELOG.md b/packages/adapter-cloudflare/CHANGELOG.md
index d7b602f66..fc1d02e4c 100644
--- a/packages/adapter-cloudflare/CHANGELOG.md
+++ b/packages/adapter-cloudflare/CHANGELOG.md
@@ -1,5 +1,29 @@
 # @sveltejs/adapter-cloudflare
 
+## 1.0.0-next.38
+
+### Patch Changes
+
+- Ensure all excluded paths in `_routes.json` start with / ([#6952](https://github.com/sveltejs/kit/pull/6952))
+
+## 1.0.0-next.37
+
+### Patch Changes
+
+- Support Cloudflare Pages `_routes.json` specification ([#6530](https://github.com/sveltejs/kit/pull/6530))
+
+## 1.0.0-next.36
+
+### Patch Changes
+
+- Include ambient.d.ts files in adapter packages. ([#6917](https://github.com/sveltejs/kit/pull/6917))
+
+## 1.0.0-next.35
+
+### Patch Changes
+
+- [fix] return 404 instead of 200 for missing assets ([#6879](https://github.com/sveltejs/kit/pull/6879))
+
 ## 1.0.0-next.34
 
 ### Patch Changes
diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts
index ed89676da..1e47e1627 100644
--- a/packages/adapter-cloudflare/index.d.ts
+++ b/packages/adapter-cloudflare/index.d.ts
@@ -2,3 +2,10 @@ import { Adapter } from '@sveltejs/kit';
 import './ambient.js';
 
 export default function plugin(): Adapter;
+
+export interface RoutesJSONSpec {
+	version: 1;
+	description: string;
+	include: string[];
+	exclude: string[];
+}
diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js
index bfb9d968a..0b6eb5c41 100644
--- a/packages/adapter-cloudflare/index.js
+++ b/packages/adapter-cloudflare/index.js
@@ -23,18 +23,28 @@ export default function () {
 			builder.rimraf(tmp);
 			builder.mkdirp(tmp);
 
-			builder.writeClient(dest);
+			const written_files = builder.writeClient(dest);
 			builder.writePrerendered(dest);
 
 			const relativePath = posix.relative(tmp, builder.getServerDirectory());
 
+			builder.log.info(
+				`adapter-cloudfare is writing its own _headers file. If you have your own, you should duplicate the headers contained in: ${dest}/_headers`
+			);
+
 			writeFileSync(
 				`${tmp}/manifest.js`,
-				`export const manifest = ${builder.generateManifest({
-					relativePath
-				})};\n\nexport const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n`
+				`export const manifest = ${builder.generateManifest({ relativePath })};\n\n` +
+					`export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n`
+			);
+
+			writeFileSync(
+				`${dest}/_routes.json`,
+				JSON.stringify(get_routes_json(builder.config.kit.appDir, written_files))
 			);
 
+			writeFileSync(`${dest}/_headers`, generate_headers(builder.config.kit.appDir));
+
 			builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, {
 				replace: {
 					SERVER: `${relativePath}/index.js`,
@@ -55,3 +65,41 @@ export default function () {
 		}
 	};
 }
+
+/**
+ * @param {string} app_dir
+ * @param {string[]} assets
+ * @returns {import('.').RoutesJSONSpec}
+ */
+function get_routes_json(app_dir, assets) {
+	return {
+		version: 1,
+		description: 'Generated by @sveltejs/adapter-cloudflare',
+		include: ['/*'],
+		exclude: [
+			`/${app_dir}/immutable/*`,
+			...assets
+				// We're being conservative by not excluding all assets in
+				// /static just yet. If there are any upstream auth rules to
+				// protect certain things (e.g. a PDF that requires auth),
+				// then we wouldn't want to prevent those requests from going
+				// to the user functions worker.
+				// We do want to show an example of a _routes.json that
+				// excludes more than just /_app/immutable/*, and favicons
+				// are a reasonable choice
+				.filter((file) => file.startsWith('favicon'))
+				.map((file) => `/${file}`)
+		]
+	};
+}
+
+/** @param {string} app_dir */
+function generate_headers(app_dir) {
+	return `
+# === START AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
+/${app_dir}/immutable/*
+	Cache-Control: public, immutable, max-age=31536000
+	X-Robots-Tag: noindex
+# === END AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
+	`.trim();
+}
diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json
index ef57ca5c6..5c180ae58 100644
--- a/packages/adapter-cloudflare/package.json
+++ b/packages/adapter-cloudflare/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/adapter-cloudflare",
-	"version": "1.0.0-next.34",
+	"version": "1.0.0-next.38",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -20,7 +20,8 @@
 	"files": [
 		"files",
 		"index.js",
-		"index.d.ts"
+		"index.d.ts",
+		"ambient.d.ts"
 	],
 	"main": "index.js",
 	"scripts": {
diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js
index a31267aef..d3223615f 100644
--- a/packages/adapter-cloudflare/src/worker.js
+++ b/packages/adapter-cloudflare/src/worker.js
@@ -20,6 +20,7 @@ const worker = {
 		// static assets
 		if (pathname.startsWith(prefix)) {
 			res = await env.ASSETS.fetch(req);
+			if (!res.ok) return res;
 
 			const cache_control = pathname.startsWith(prefix + 'immutable/')
 				? 'public, immutable, max-age=31536000'
@@ -65,7 +66,7 @@ const worker = {
 
 		// Writes to Cache only if allowed & specified
 		pragma = res.headers.get('cache-control');
-		return pragma ? Cache.save(req, res, context) : res;
+		return pragma && res.ok ? Cache.save(req, res, context) : res;
 	}
 };
 
diff --git a/packages/adapter-node/CHANGELOG.md b/packages/adapter-node/CHANGELOG.md
index 43ac0fa07..4871493d2 100644
--- a/packages/adapter-node/CHANGELOG.md
+++ b/packages/adapter-node/CHANGELOG.md
@@ -1,5 +1,23 @@
 # @sveltejs/adapter-node
 
+## 1.0.0-next.95
+
+### Patch Changes
+
+- Redact error message if `getRequest` fails ([#6936](https://github.com/sveltejs/kit/pull/6936))
+
+## 1.0.0-next.94
+
+### Patch Changes
+
+- Add missing Rollup dependency ([#6927](https://github.com/sveltejs/kit/pull/6927))
+
+## 1.0.0-next.93
+
+### Patch Changes
+
+- Bundle with rollup instead of esbuild ([#6896](https://github.com/sveltejs/kit/pull/6896))
+
 ## 1.0.0-next.92
 
 ### Patch Changes
diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js
index cc3d0da78..17bb5310c 100644
--- a/packages/adapter-node/index.js
+++ b/packages/adapter-node/index.js
@@ -1,6 +1,9 @@
 import { readFileSync, writeFileSync } from 'fs';
 import { fileURLToPath } from 'url';
-import * as esbuild from 'esbuild';
+import { rollup } from 'rollup';
+import { nodeResolve } from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import json from '@rollup/plugin-json';
 
 const files = fileURLToPath(new URL('./files', import.meta.url).href);
 
@@ -49,16 +52,23 @@ export default function (opts = {}) {
 
 			const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
 
-			await esbuild.build({
-				platform: 'node',
-				sourcemap: 'linked',
-				target: 'es2022',
-				entryPoints: [`${tmp}/index.js`, `${tmp}/manifest.js`],
-				outdir: `${out}/server`,
-				splitting: true,
+			// we bundle the Vite output so that deployments only need
+			// their production dependencies. Anything in devDependencies
+			// will get included in the bundled code
+			const bundle = await rollup({
+				input: {
+					index: `${tmp}/index.js`,
+					manifest: `${tmp}/manifest.js`
+				},
+				external: [...Object.keys(pkg.dependencies || {})],
+				plugins: [nodeResolve(), commonjs(), json()]
+			});
+
+			await bundle.write({
+				dir: `${out}/server`,
 				format: 'esm',
-				bundle: true,
-				external: [...Object.keys(pkg.dependencies || {})]
+				sourcemap: true,
+				chunkFileNames: `chunks/[name]-[hash].js`
 			});
 
 			builder.copy(files, out, {
diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json
index c5d40c7fb..c10387b3f 100644
--- a/packages/adapter-node/package.json
+++ b/packages/adapter-node/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/adapter-node",
-	"version": "1.0.0-next.92",
+	"version": "1.0.0-next.95",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -32,19 +32,20 @@
 		"prepublishOnly": "npm run build"
 	},
 	"devDependencies": {
-		"@rollup/plugin-json": "^4.1.0",
 		"@sveltejs/kit": "workspace:*",
 		"@types/node": "^16.11.36",
 		"c8": "^7.11.3",
 		"node-fetch": "^3.2.4",
 		"polka": "^1.0.0-next.22",
 		"rimraf": "^3.0.2",
-		"rollup": "^2.78.1",
 		"sirv": "^2.0.2",
 		"typescript": "^4.8.2",
 		"uvu": "^0.5.3"
 	},
 	"dependencies": {
-		"esbuild": "^0.15.7"
+		"@rollup/plugin-commonjs": "^22.0.1",
+		"@rollup/plugin-json": "^4.1.0",
+		"@rollup/plugin-node-resolve": "^14.1.0",
+		"rollup": "^2.78.1"
 	}
 }
diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js
index 94f331f57..7177e836b 100644
--- a/packages/adapter-node/src/handler.js
+++ b/packages/adapter-node/src/handler.js
@@ -56,7 +56,7 @@ const ssr = async (req, res) => {
 		});
 	} catch (err) {
 		res.statusCode = err.status || 400;
-		res.end(err.reason || 'Invalid request body');
+		res.end('Invalid request body');
 		return;
 	}
 
diff --git a/packages/adapter-vercel/CHANGELOG.md b/packages/adapter-vercel/CHANGELOG.md
index 8f89e664f..8d4d31b63 100644
--- a/packages/adapter-vercel/CHANGELOG.md
+++ b/packages/adapter-vercel/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/adapter-vercel
 
+## 1.0.0-next.77
+
+### Patch Changes
+
+- Redact error message if `getRequest` fails ([#6936](https://github.com/sveltejs/kit/pull/6936))
+
 ## 1.0.0-next.76
 
 ### Patch Changes
diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/files/serverless.js
index 36fbae4c7..25db646be 100644
--- a/packages/adapter-vercel/files/serverless.js
+++ b/packages/adapter-vercel/files/serverless.js
@@ -23,7 +23,7 @@ export default async (req, res) => {
 		request = await getRequest({ base: `https://${req.headers.host}`, request: req });
 	} catch (err) {
 		res.statusCode = err.status || 400;
-		return res.end(err.reason || 'Invalid request body');
+		return res.end('Invalid request body');
 	}
 
 	setResponse(
diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json
index 6cd9ec393..3a81edde3 100644
--- a/packages/adapter-vercel/package.json
+++ b/packages/adapter-vercel/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/adapter-vercel",
-	"version": "1.0.0-next.76",
+	"version": "1.0.0-next.77",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index af144415f..85c84998e 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,79 @@
 # @sveltejs/kit
 
+## 1.0.0-next.495
+
+### Patch Changes
+
+- Run `svelte-kit sync` in all workspace directories during postinstall ([#6949](https://github.com/sveltejs/kit/pull/6949))
+
+## 1.0.0-next.494
+
+### Patch Changes
+
+- Fix fetch type ([#6955](https://github.com/sveltejs/kit/pull/6955))
+
+* Add csp wasm-unsafe-eval keyword ([#6958](https://github.com/sveltejs/kit/pull/6958))
+
+- Register service worker with relative URL ([#6954](https://github.com/sveltejs/kit/pull/6954))
+
+## 1.0.0-next.493
+
+### Patch Changes
+
+- [fix] allow missing routes folder ([#6944](https://github.com/sveltejs/kit/pull/6944))
+
+* Only forward set-cookie headers for internal fetches ([#6923](https://github.com/sveltejs/kit/pull/6923))
+
+## 1.0.0-next.492
+
+### Patch Changes
+
+- [fix] add missing depends function to ServerLoadEvent type ([#6935](https://github.com/sveltejs/kit/pull/6935))
+
+* Redact error message if `getRequest` fails ([#6936](https://github.com/sveltejs/kit/pull/6936))
+
+- Add cookies.serialize method ([#6925](https://github.com/sveltejs/kit/pull/6925))
+
+* Always apply cookies, not just for matched routes ([#6925](https://github.com/sveltejs/kit/pull/6925))
+
+- [fix] tighten up navigation and invalidation logic ([#6924](https://github.com/sveltejs/kit/pull/6924))
+
+* Respond with 413 if request body is too large ([#6936](https://github.com/sveltejs/kit/pull/6936))
+
+## 1.0.0-next.491
+
+### Patch Changes
+
+- [fix] symlink routes ([#6796](https://github.com/sveltejs/kit/pull/6796))
+
+## 1.0.0-next.490
+
+### Patch Changes
+
+- fix `write_types` on windows using posixify() ([#6913](https://github.com/sveltejs/kit/pull/6913))
+
+* Prevent caching of `__data.js` files ([#6904](https://github.com/sveltejs/kit/pull/6904))
+
+- Make url property getters non-enumerable ([#6909](https://github.com/sveltejs/kit/pull/6909))
+
+## 1.0.0-next.489
+
+### Patch Changes
+
+- [fix] tighten up handling and documentation around 404 ([#6897](https://github.com/sveltejs/kit/pull/6897))
+
+## 1.0.0-next.488
+
+### Patch Changes
+
+- Decode HTML entities in `href` attributes when crawling ([#6891](https://github.com/sveltejs/kit/pull/6891))
+
+## 1.0.0-next.487
+
+### Patch Changes
+
+- [feat] allow +server.js files next to +page files ([#6773](https://github.com/sveltejs/kit/pull/6773))
+
 ## 1.0.0-next.486
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 1ac67552d..ec0334c80 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.486",
+	"version": "1.0.0-next.495",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -54,6 +54,7 @@
 		"!src/core/**/test",
 		"types",
 		"svelte-kit.js",
+		"postinstall.js",
 		"scripts/special-types"
 	],
 	"scripts": {
@@ -67,7 +68,7 @@
 		"test:integration": "pnpm run -r --workspace-concurrency 1 --filter=\"./test/**\" test",
 		"test:unit": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\"",
 		"types": "node scripts/extract-types.js",
-		"postinstall": "node svelte-kit.js sync"
+		"postinstall": "node postinstall.js"
 	},
 	"exports": {
 		"./package.json": "./package.json",
diff --git a/packages/kit/postinstall.js b/packages/kit/postinstall.js
new file mode 100644
index 000000000..980337e5b
--- /dev/null
+++ b/packages/kit/postinstall.js
@@ -0,0 +1,37 @@
+import fs from 'fs';
+import path from 'path';
+import glob from 'tiny-glob/sync.js';
+import { load_config } from './src/core/config/index.js';
+import * as sync from './src/core/sync/sync.js';
+
+const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
+
+const directories = [];
+
+const cwd = process.env.INIT_CWD ?? process.cwd();
+
+if (pkg.workspaces) {
+	for (const directory of pkg.workspaces) {
+		directories.push(...glob(directory, { cwd }).map((dir) => path.resolve(cwd, dir)));
+	}
+} else {
+	directories.push(cwd);
+}
+
+for (const cwd of directories) {
+	process.chdir(cwd);
+
+	if (!fs.existsSync('package.json')) continue;
+	if (!fs.existsSync('svelte.config.js')) continue;
+
+	const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
+	if (!pkg.dependencies?.['@sveltejs/kit'] && !pkg.devDependencies?.['@sveltejs/kit']) continue;
+
+	try {
+		const config = await load_config();
+		await sync.all(config, 'development');
+	} catch (e) {
+		console.log('Error while trying to sync SvelteKit config');
+		console.log(e);
+	}
+}
diff --git a/packages/kit/scripts/extract-types.js b/packages/kit/scripts/extract-types.js
index f06110520..36a95b78d 100644
--- a/packages/kit/scripts/extract-types.js
+++ b/packages/kit/scripts/extract-types.js
@@ -22,6 +22,11 @@ function get_types(code, statements) {
 
 	if (statements) {
 		for (const statement of statements) {
+			const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;
+
+			const export_modifier = modifiers?.find((modifier) => modifier.kind === 93);
+			if (!export_modifier) continue;
+
 			if (
 				ts.isClassDeclaration(statement) ||
 				ts.isInterfaceDeclaration(statement) ||
diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js
index 98348e6f0..6e931aaf9 100755
--- a/packages/kit/src/cli.js
+++ b/packages/kit/src/cli.js
@@ -41,10 +41,6 @@ prog
 			return;
 		}
 
-		if (event === 'postinstall' && process.env.INIT_CWD) {
-			process.chdir(process.env.INIT_CWD);
-		}
-
 		if (!fs.existsSync('svelte.config.js')) {
 			console.warn(`Missing ${path.resolve('svelte.config.js')} — skipping`);
 			return;
diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js
index b79fb68d7..f8dcc36d1 100644
--- a/packages/kit/src/core/adapt/builder.js
+++ b/packages/kit/src/core/adapt/builder.js
@@ -46,7 +46,6 @@ export function create_builder({ config, build_data, routes, prerendered, log })
 
 				return {
 					id: route.id,
-					type: route.page ? 'page' : 'endpoint', // TODO change this if support pages+endpoints
 					segments: route.id.split('/').map((segment) => ({
 						dynamic: segment.includes('['),
 						rest: segment.includes('[...'),
diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js
index d0e35d59f..91ec76997 100644
--- a/packages/kit/src/core/generate_manifest/index.js
+++ b/packages/kit/src/core/generate_manifest/index.js
@@ -1,5 +1,6 @@
 import { s } from '../../utils/misc.js';
 import { get_mime_lookup } from '../utils.js';
+import { resolve_symlinks } from '../../exports/vite/build/utils.js';
 
 /**
  * Generates the data used to write the server-side manifest.js file. This data is used in the Vite
@@ -65,7 +66,7 @@ export function generate_manifest({ build_data, relative_path, routes, format =
 					names: ${s(route.names)},
 					types: ${s(route.types)},
 					page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${route.page.leaf} }` : 'null'},
-					endpoint: ${route.endpoint ? loader(`${relative_path}/${build_data.server.vite_manifest[route.endpoint.file].file}`) : 'null'}
+					endpoint: ${route.endpoint ? loader(`${relative_path}/${resolve_symlinks(build_data.server.vite_manifest, route.endpoint.file).chunk.file}`) : 'null'}
 				}`;
 				}).filter(Boolean).join(',\n\t\t\t\t')}
 			],
diff --git a/packages/kit/src/core/prerender/crawl.js b/packages/kit/src/core/prerender/crawl.js
index b74e558d6..70c264658 100644
--- a/packages/kit/src/core/prerender/crawl.js
+++ b/packages/kit/src/core/prerender/crawl.js
@@ -1,3 +1,5 @@
+import { decode } from './entities.js';
+
 const DOCTYPE = 'DOCTYPE';
 const CDATA_OPEN = '[CDATA[';
 const CDATA_CLOSE = ']]>';
@@ -147,6 +149,8 @@ export function crawl(html) {
 								i -= 1;
 							}
 
+							value = decode(value);
+
 							if (name === 'href') {
 								href = value;
 							} else if (name === 'rel') {
diff --git a/packages/kit/src/core/prerender/entities.js b/packages/kit/src/core/prerender/entities.js
new file mode 100644
index 000000000..91888a95b
--- /dev/null
+++ b/packages/kit/src/core/prerender/entities.js
@@ -0,0 +1,2252 @@
+/**
+ * A list of all named HTML entities, from https://html.spec.whatwg.org/multipage/named-characters.html
+ * @type {Record<string, string>}
+ */
+const entities = {
+	AElig: 'Æ',
+	'AElig;': 'Æ',
+	AMP: '&',
+	'AMP;': '&',
+	Aacute: 'Á',
+	'Aacute;': 'Á',
+	'Abreve;': 'Ă',
+	Acirc: 'Â',
+	'Acirc;': 'Â',
+	'Acy;': 'А',
+	'Afr;': '𝔄',
+	Agrave: 'À',
+	'Agrave;': 'À',
+	'Alpha;': 'Α',
+	'Amacr;': 'Ā',
+	'And;': '⩓',
+	'Aogon;': 'Ą',
+	'Aopf;': '𝔸',
+	'ApplyFunction;': '⁡',
+	Aring: 'Å',
+	'Aring;': 'Å',
+	'Ascr;': '𝒜',
+	'Assign;': '≔',
+	Atilde: 'Ã',
+	'Atilde;': 'Ã',
+	Auml: 'Ä',
+	'Auml;': 'Ä',
+	'Backslash;': '∖',
+	'Barv;': '⫧',
+	'Barwed;': '⌆',
+	'Bcy;': 'Б',
+	'Because;': '∵',
+	'Bernoullis;': 'ℬ',
+	'Beta;': 'Β',
+	'Bfr;': '𝔅',
+	'Bopf;': '𝔹',
+	'Breve;': '˘',
+	'Bscr;': 'ℬ',
+	'Bumpeq;': '≎',
+	'CHcy;': 'Ч',
+	COPY: '©',
+	'COPY;': '©',
+	'Cacute;': 'Ć',
+	'Cap;': '⋒',
+	'CapitalDifferentialD;': 'ⅅ',
+	'Cayleys;': 'ℭ',
+	'Ccaron;': 'Č',
+	Ccedil: 'Ç',
+	'Ccedil;': 'Ç',
+	'Ccirc;': 'Ĉ',
+	'Cconint;': '∰',
+	'Cdot;': 'Ċ',
+	'Cedilla;': '¸',
+	'CenterDot;': '·',
+	'Cfr;': 'ℭ',
+	'Chi;': 'Χ',
+	'CircleDot;': '⊙',
+	'CircleMinus;': '⊖',
+	'CirclePlus;': '⊕',
+	'CircleTimes;': '⊗',
+	'ClockwiseContourIntegral;': '∲',
+	'CloseCurlyDoubleQuote;': '”',
+	'CloseCurlyQuote;': '’',
+	'Colon;': '∷',
+	'Colone;': '⩴',
+	'Congruent;': '≡',
+	'Conint;': '∯',
+	'ContourIntegral;': '∮',
+	'Copf;': 'ℂ',
+	'Coproduct;': '∐',
+	'CounterClockwiseContourIntegral;': '∳',
+	'Cross;': '⨯',
+	'Cscr;': '𝒞',
+	'Cup;': '⋓',
+	'CupCap;': '≍',
+	'DD;': 'ⅅ',
+	'DDotrahd;': '⤑',
+	'DJcy;': 'Ђ',
+	'DScy;': 'Ѕ',
+	'DZcy;': 'Џ',
+	'Dagger;': '‡',
+	'Darr;': '↡',
+	'Dashv;': '⫤',
+	'Dcaron;': 'Ď',
+	'Dcy;': 'Д',
+	'Del;': '∇',
+	'Delta;': 'Δ',
+	'Dfr;': '𝔇',
+	'DiacriticalAcute;': '´',
+	'DiacriticalDot;': '˙',
+	'DiacriticalDoubleAcute;': '˝',
+	'DiacriticalGrave;': '`',
+	'DiacriticalTilde;': '˜',
+	'Diamond;': '⋄',
+	'DifferentialD;': 'ⅆ',
+	'Dopf;': '𝔻',
+	'Dot;': '¨',
+	'DotDot;': '⃜',
+	'DotEqual;': '≐',
+	'DoubleContourIntegral;': '∯',
+	'DoubleDot;': '¨',
+	'DoubleDownArrow;': '⇓',
+	'DoubleLeftArrow;': '⇐',
+	'DoubleLeftRightArrow;': '⇔',
+	'DoubleLeftTee;': '⫤',
+	'DoubleLongLeftArrow;': '⟸',
+	'DoubleLongLeftRightArrow;': '⟺',
+	'DoubleLongRightArrow;': '⟹',
+	'DoubleRightArrow;': '⇒',
+	'DoubleRightTee;': '⊨',
+	'DoubleUpArrow;': '⇑',
+	'DoubleUpDownArrow;': '⇕',
+	'DoubleVerticalBar;': '∥',
+	'DownArrow;': '↓',
+	'DownArrowBar;': '⤓',
+	'DownArrowUpArrow;': '⇵',
+	'DownBreve;': '̑',
+	'DownLeftRightVector;': '⥐',
+	'DownLeftTeeVector;': '⥞',
+	'DownLeftVector;': '↽',
+	'DownLeftVectorBar;': '⥖',
+	'DownRightTeeVector;': '⥟',
+	'DownRightVector;': '⇁',
+	'DownRightVectorBar;': '⥗',
+	'DownTee;': '⊤',
+	'DownTeeArrow;': '↧',
+	'Downarrow;': '⇓',
+	'Dscr;': '𝒟',
+	'Dstrok;': 'Đ',
+	'ENG;': 'Ŋ',
+	ETH: 'Ð',
+	'ETH;': 'Ð',
+	Eacute: 'É',
+	'Eacute;': 'É',
+	'Ecaron;': 'Ě',
+	Ecirc: 'Ê',
+	'Ecirc;': 'Ê',
+	'Ecy;': 'Э',
+	'Edot;': 'Ė',
+	'Efr;': '𝔈',
+	Egrave: 'È',
+	'Egrave;': 'È',
+	'Element;': '∈',
+	'Emacr;': 'Ē',
+	'EmptySmallSquare;': '◻',
+	'EmptyVerySmallSquare;': '▫',
+	'Eogon;': 'Ę',
+	'Eopf;': '𝔼',
+	'Epsilon;': 'Ε',
+	'Equal;': '⩵',
+	'EqualTilde;': '≂',
+	'Equilibrium;': '⇌',
+	'Escr;': 'ℰ',
+	'Esim;': '⩳',
+	'Eta;': 'Η',
+	Euml: 'Ë',
+	'Euml;': 'Ë',
+	'Exists;': '∃',
+	'ExponentialE;': 'ⅇ',
+	'Fcy;': 'Ф',
+	'Ffr;': '𝔉',
+	'FilledSmallSquare;': '◼',
+	'FilledVerySmallSquare;': '▪',
+	'Fopf;': '𝔽',
+	'ForAll;': '∀',
+	'Fouriertrf;': 'ℱ',
+	'Fscr;': 'ℱ',
+	'GJcy;': 'Ѓ',
+	GT: '>',
+	'GT;': '>',
+	'Gamma;': 'Γ',
+	'Gammad;': 'Ϝ',
+	'Gbreve;': 'Ğ',
+	'Gcedil;': 'Ģ',
+	'Gcirc;': 'Ĝ',
+	'Gcy;': 'Г',
+	'Gdot;': 'Ġ',
+	'Gfr;': '𝔊',
+	'Gg;': '⋙',
+	'Gopf;': '𝔾',
+	'GreaterEqual;': '≥',
+	'GreaterEqualLess;': '⋛',
+	'GreaterFullEqual;': '≧',
+	'GreaterGreater;': '⪢',
+	'GreaterLess;': '≷',
+	'GreaterSlantEqual;': '⩾',
+	'GreaterTilde;': '≳',
+	'Gscr;': '𝒢',
+	'Gt;': '≫',
+	'HARDcy;': 'Ъ',
+	'Hacek;': 'ˇ',
+	'Hat;': '^',
+	'Hcirc;': 'Ĥ',
+	'Hfr;': 'ℌ',
+	'HilbertSpace;': 'ℋ',
+	'Hopf;': 'ℍ',
+	'HorizontalLine;': '─',
+	'Hscr;': 'ℋ',
+	'Hstrok;': 'Ħ',
+	'HumpDownHump;': '≎',
+	'HumpEqual;': '≏',
+	'IEcy;': 'Е',
+	'IJlig;': 'Ĳ',
+	'IOcy;': 'Ё',
+	Iacute: 'Í',
+	'Iacute;': 'Í',
+	Icirc: 'Î',
+	'Icirc;': 'Î',
+	'Icy;': 'И',
+	'Idot;': 'İ',
+	'Ifr;': 'ℑ',
+	Igrave: 'Ì',
+	'Igrave;': 'Ì',
+	'Im;': 'ℑ',
+	'Imacr;': 'Ī',
+	'ImaginaryI;': 'ⅈ',
+	'Implies;': '⇒',
+	'Int;': '∬',
+	'Integral;': '∫',
+	'Intersection;': '⋂',
+	'InvisibleComma;': '⁣',
+	'InvisibleTimes;': '⁢',
+	'Iogon;': 'Į',
+	'Iopf;': '𝕀',
+	'Iota;': 'Ι',
+	'Iscr;': 'ℐ',
+	'Itilde;': 'Ĩ',
+	'Iukcy;': 'І',
+	Iuml: 'Ï',
+	'Iuml;': 'Ï',
+	'Jcirc;': 'Ĵ',
+	'Jcy;': 'Й',
+	'Jfr;': '𝔍',
+	'Jopf;': '𝕁',
+	'Jscr;': '𝒥',
+	'Jsercy;': 'Ј',
+	'Jukcy;': 'Є',
+	'KHcy;': 'Х',
+	'KJcy;': 'Ќ',
+	'Kappa;': 'Κ',
+	'Kcedil;': 'Ķ',
+	'Kcy;': 'К',
+	'Kfr;': '𝔎',
+	'Kopf;': '𝕂',
+	'Kscr;': '𝒦',
+	'LJcy;': 'Љ',
+	LT: '<',
+	'LT;': '<',
+	'Lacute;': 'Ĺ',
+	'Lambda;': 'Λ',
+	'Lang;': '⟪',
+	'Laplacetrf;': 'ℒ',
+	'Larr;': '↞',
+	'Lcaron;': 'Ľ',
+	'Lcedil;': 'Ļ',
+	'Lcy;': 'Л',
+	'LeftAngleBracket;': '⟨',
+	'LeftArrow;': '←',
+	'LeftArrowBar;': '⇤',
+	'LeftArrowRightArrow;': '⇆',
+	'LeftCeiling;': '⌈',
+	'LeftDoubleBracket;': '⟦',
+	'LeftDownTeeVector;': '⥡',
+	'LeftDownVector;': '⇃',
+	'LeftDownVectorBar;': '⥙',
+	'LeftFloor;': '⌊',
+	'LeftRightArrow;': '↔',
+	'LeftRightVector;': '⥎',
+	'LeftTee;': '⊣',
+	'LeftTeeArrow;': '↤',
+	'LeftTeeVector;': '⥚',
+	'LeftTriangle;': '⊲',
+	'LeftTriangleBar;': '⧏',
+	'LeftTriangleEqual;': '⊴',
+	'LeftUpDownVector;': '⥑',
+	'LeftUpTeeVector;': '⥠',
+	'LeftUpVector;': '↿',
+	'LeftUpVectorBar;': '⥘',
+	'LeftVector;': '↼',
+	'LeftVectorBar;': '⥒',
+	'Leftarrow;': '⇐',
+	'Leftrightarrow;': '⇔',
+	'LessEqualGreater;': '⋚',
+	'LessFullEqual;': '≦',
+	'LessGreater;': '≶',
+	'LessLess;': '⪡',
+	'LessSlantEqual;': '⩽',
+	'LessTilde;': '≲',
+	'Lfr;': '𝔏',
+	'Ll;': '⋘',
+	'Lleftarrow;': '⇚',
+	'Lmidot;': 'Ŀ',
+	'LongLeftArrow;': '⟵',
+	'LongLeftRightArrow;': '⟷',
+	'LongRightArrow;': '⟶',
+	'Longleftarrow;': '⟸',
+	'Longleftrightarrow;': '⟺',
+	'Longrightarrow;': '⟹',
+	'Lopf;': '𝕃',
+	'LowerLeftArrow;': '↙',
+	'LowerRightArrow;': '↘',
+	'Lscr;': 'ℒ',
+	'Lsh;': '↰',
+	'Lstrok;': 'Ł',
+	'Lt;': '≪',
+	'Map;': '⤅',
+	'Mcy;': 'М',
+	'MediumSpace;': ' ',
+	'Mellintrf;': 'ℳ',
+	'Mfr;': '𝔐',
+	'MinusPlus;': '∓',
+	'Mopf;': '𝕄',
+	'Mscr;': 'ℳ',
+	'Mu;': 'Μ',
+	'NJcy;': 'Њ',
+	'Nacute;': 'Ń',
+	'Ncaron;': 'Ň',
+	'Ncedil;': 'Ņ',
+	'Ncy;': 'Н',
+	'NegativeMediumSpace;': '​',
+	'NegativeThickSpace;': '​',
+	'NegativeThinSpace;': '​',
+	'NegativeVeryThinSpace;': '​',
+	'NestedGreaterGreater;': '≫',
+	'NestedLessLess;': '≪',
+	'NewLine;': '\n',
+	'Nfr;': '𝔑',
+	'NoBreak;': '⁠',
+	'NonBreakingSpace;': ' ',
+	'Nopf;': 'ℕ',
+	'Not;': '⫬',
+	'NotCongruent;': '≢',
+	'NotCupCap;': '≭',
+	'NotDoubleVerticalBar;': '∦',
+	'NotElement;': '∉',
+	'NotEqual;': '≠',
+	'NotEqualTilde;': '≂̸',
+	'NotExists;': '∄',
+	'NotGreater;': '≯',
+	'NotGreaterEqual;': '≱',
+	'NotGreaterFullEqual;': '≧̸',
+	'NotGreaterGreater;': '≫̸',
+	'NotGreaterLess;': '≹',
+	'NotGreaterSlantEqual;': '⩾̸',
+	'NotGreaterTilde;': '≵',
+	'NotHumpDownHump;': '≎̸',
+	'NotHumpEqual;': '≏̸',
+	'NotLeftTriangle;': '⋪',
+	'NotLeftTriangleBar;': '⧏̸',
+	'NotLeftTriangleEqual;': '⋬',
+	'NotLess;': '≮',
+	'NotLessEqual;': '≰',
+	'NotLessGreater;': '≸',
+	'NotLessLess;': '≪̸',
+	'NotLessSlantEqual;': '⩽̸',
+	'NotLessTilde;': '≴',
+	'NotNestedGreaterGreater;': '⪢̸',
+	'NotNestedLessLess;': '⪡̸',
+	'NotPrecedes;': '⊀',
+	'NotPrecedesEqual;': '⪯̸',
+	'NotPrecedesSlantEqual;': '⋠',
+	'NotReverseElement;': '∌',
+	'NotRightTriangle;': '⋫',
+	'NotRightTriangleBar;': '⧐̸',
+	'NotRightTriangleEqual;': '⋭',
+	'NotSquareSubset;': '⊏̸',
+	'NotSquareSubsetEqual;': '⋢',
+	'NotSquareSuperset;': '⊐̸',
+	'NotSquareSupersetEqual;': '⋣',
+	'NotSubset;': '⊂⃒',
+	'NotSubsetEqual;': '⊈',
+	'NotSucceeds;': '⊁',
+	'NotSucceedsEqual;': '⪰̸',
+	'NotSucceedsSlantEqual;': '⋡',
+	'NotSucceedsTilde;': '≿̸',
+	'NotSuperset;': '⊃⃒',
+	'NotSupersetEqual;': '⊉',
+	'NotTilde;': '≁',
+	'NotTildeEqual;': '≄',
+	'NotTildeFullEqual;': '≇',
+	'NotTildeTilde;': '≉',
+	'NotVerticalBar;': '∤',
+	'Nscr;': '𝒩',
+	Ntilde: 'Ñ',
+	'Ntilde;': 'Ñ',
+	'Nu;': 'Ν',
+	'OElig;': 'Œ',
+	Oacute: 'Ó',
+	'Oacute;': 'Ó',
+	Ocirc: 'Ô',
+	'Ocirc;': 'Ô',
+	'Ocy;': 'О',
+	'Odblac;': 'Ő',
+	'Ofr;': '𝔒',
+	Ograve: 'Ò',
+	'Ograve;': 'Ò',
+	'Omacr;': 'Ō',
+	'Omega;': 'Ω',
+	'Omicron;': 'Ο',
+	'Oopf;': '𝕆',
+	'OpenCurlyDoubleQuote;': '“',
+	'OpenCurlyQuote;': '‘',
+	'Or;': '⩔',
+	'Oscr;': '𝒪',
+	Oslash: 'Ø',
+	'Oslash;': 'Ø',
+	Otilde: 'Õ',
+	'Otilde;': 'Õ',
+	'Otimes;': '⨷',
+	Ouml: 'Ö',
+	'Ouml;': 'Ö',
+	'OverBar;': '‾',
+	'OverBrace;': '⏞',
+	'OverBracket;': '⎴',
+	'OverParenthesis;': '⏜',
+	'PartialD;': '∂',
+	'Pcy;': 'П',
+	'Pfr;': '𝔓',
+	'Phi;': 'Φ',
+	'Pi;': 'Π',
+	'PlusMinus;': '±',
+	'Poincareplane;': 'ℌ',
+	'Popf;': 'ℙ',
+	'Pr;': '⪻',
+	'Precedes;': '≺',
+	'PrecedesEqual;': '⪯',
+	'PrecedesSlantEqual;': '≼',
+	'PrecedesTilde;': '≾',
+	'Prime;': '″',
+	'Product;': '∏',
+	'Proportion;': '∷',
+	'Proportional;': '∝',
+	'Pscr;': '𝒫',
+	'Psi;': 'Ψ',
+	QUOT: '"',
+	'QUOT;': '"',
+	'Qfr;': '𝔔',
+	'Qopf;': 'ℚ',
+	'Qscr;': '𝒬',
+	'RBarr;': '⤐',
+	REG: '®',
+	'REG;': '®',
+	'Racute;': 'Ŕ',
+	'Rang;': '⟫',
+	'Rarr;': '↠',
+	'Rarrtl;': '⤖',
+	'Rcaron;': 'Ř',
+	'Rcedil;': 'Ŗ',
+	'Rcy;': 'Р',
+	'Re;': 'ℜ',
+	'ReverseElement;': '∋',
+	'ReverseEquilibrium;': '⇋',
+	'ReverseUpEquilibrium;': '⥯',
+	'Rfr;': 'ℜ',
+	'Rho;': 'Ρ',
+	'RightAngleBracket;': '⟩',
+	'RightArrow;': '→',
+	'RightArrowBar;': '⇥',
+	'RightArrowLeftArrow;': '⇄',
+	'RightCeiling;': '⌉',
+	'RightDoubleBracket;': '⟧',
+	'RightDownTeeVector;': '⥝',
+	'RightDownVector;': '⇂',
+	'RightDownVectorBar;': '⥕',
+	'RightFloor;': '⌋',
+	'RightTee;': '⊢',
+	'RightTeeArrow;': '↦',
+	'RightTeeVector;': '⥛',
+	'RightTriangle;': '⊳',
+	'RightTriangleBar;': '⧐',
+	'RightTriangleEqual;': '⊵',
+	'RightUpDownVector;': '⥏',
+	'RightUpTeeVector;': '⥜',
+	'RightUpVector;': '↾',
+	'RightUpVectorBar;': '⥔',
+	'RightVector;': '⇀',
+	'RightVectorBar;': '⥓',
+	'Rightarrow;': '⇒',
+	'Ropf;': 'ℝ',
+	'RoundImplies;': '⥰',
+	'Rrightarrow;': '⇛',
+	'Rscr;': 'ℛ',
+	'Rsh;': '↱',
+	'RuleDelayed;': '⧴',
+	'SHCHcy;': 'Щ',
+	'SHcy;': 'Ш',
+	'SOFTcy;': 'Ь',
+	'Sacute;': 'Ś',
+	'Sc;': '⪼',
+	'Scaron;': 'Š',
+	'Scedil;': 'Ş',
+	'Scirc;': 'Ŝ',
+	'Scy;': 'С',
+	'Sfr;': '𝔖',
+	'ShortDownArrow;': '↓',
+	'ShortLeftArrow;': '←',
+	'ShortRightArrow;': '→',
+	'ShortUpArrow;': '↑',
+	'Sigma;': 'Σ',
+	'SmallCircle;': '∘',
+	'Sopf;': '𝕊',
+	'Sqrt;': '√',
+	'Square;': '□',
+	'SquareIntersection;': '⊓',
+	'SquareSubset;': '⊏',
+	'SquareSubsetEqual;': '⊑',
+	'SquareSuperset;': '⊐',
+	'SquareSupersetEqual;': '⊒',
+	'SquareUnion;': '⊔',
+	'Sscr;': '𝒮',
+	'Star;': '⋆',
+	'Sub;': '⋐',
+	'Subset;': '⋐',
+	'SubsetEqual;': '⊆',
+	'Succeeds;': '≻',
+	'SucceedsEqual;': '⪰',
+	'SucceedsSlantEqual;': '≽',
+	'SucceedsTilde;': '≿',
+	'SuchThat;': '∋',
+	'Sum;': '∑',
+	'Sup;': '⋑',
+	'Superset;': '⊃',
+	'SupersetEqual;': '⊇',
+	'Supset;': '⋑',
+	THORN: 'Þ',
+	'THORN;': 'Þ',
+	'TRADE;': '™',
+	'TSHcy;': 'Ћ',
+	'TScy;': 'Ц',
+	'Tab;': '\t',
+	'Tau;': 'Τ',
+	'Tcaron;': 'Ť',
+	'Tcedil;': 'Ţ',
+	'Tcy;': 'Т',
+	'Tfr;': '𝔗',
+	'Therefore;': '∴',
+	'Theta;': 'Θ',
+	'ThickSpace;': '  ',
+	'ThinSpace;': ' ',
+	'Tilde;': '∼',
+	'TildeEqual;': '≃',
+	'TildeFullEqual;': '≅',
+	'TildeTilde;': '≈',
+	'Topf;': '𝕋',
+	'TripleDot;': '⃛',
+	'Tscr;': '𝒯',
+	'Tstrok;': 'Ŧ',
+	Uacute: 'Ú',
+	'Uacute;': 'Ú',
+	'Uarr;': '↟',
+	'Uarrocir;': '⥉',
+	'Ubrcy;': 'Ў',
+	'Ubreve;': 'Ŭ',
+	Ucirc: 'Û',
+	'Ucirc;': 'Û',
+	'Ucy;': 'У',
+	'Udblac;': 'Ű',
+	'Ufr;': '𝔘',
+	Ugrave: 'Ù',
+	'Ugrave;': 'Ù',
+	'Umacr;': 'Ū',
+	'UnderBar;': '_',
+	'UnderBrace;': '⏟',
+	'UnderBracket;': '⎵',
+	'UnderParenthesis;': '⏝',
+	'Union;': '⋃',
+	'UnionPlus;': '⊎',
+	'Uogon;': 'Ų',
+	'Uopf;': '𝕌',
+	'UpArrow;': '↑',
+	'UpArrowBar;': '⤒',
+	'UpArrowDownArrow;': '⇅',
+	'UpDownArrow;': '↕',
+	'UpEquilibrium;': '⥮',
+	'UpTee;': '⊥',
+	'UpTeeArrow;': '↥',
+	'Uparrow;': '⇑',
+	'Updownarrow;': '⇕',
+	'UpperLeftArrow;': '↖',
+	'UpperRightArrow;': '↗',
+	'Upsi;': 'ϒ',
+	'Upsilon;': 'Υ',
+	'Uring;': 'Ů',
+	'Uscr;': '𝒰',
+	'Utilde;': 'Ũ',
+	Uuml: 'Ü',
+	'Uuml;': 'Ü',
+	'VDash;': '⊫',
+	'Vbar;': '⫫',
+	'Vcy;': 'В',
+	'Vdash;': '⊩',
+	'Vdashl;': '⫦',
+	'Vee;': '⋁',
+	'Verbar;': '‖',
+	'Vert;': '‖',
+	'VerticalBar;': '∣',
+	'VerticalLine;': '|',
+	'VerticalSeparator;': '❘',
+	'VerticalTilde;': '≀',
+	'VeryThinSpace;': ' ',
+	'Vfr;': '𝔙',
+	'Vopf;': '𝕍',
+	'Vscr;': '𝒱',
+	'Vvdash;': '⊪',
+	'Wcirc;': 'Ŵ',
+	'Wedge;': '⋀',
+	'Wfr;': '𝔚',
+	'Wopf;': '𝕎',
+	'Wscr;': '𝒲',
+	'Xfr;': '𝔛',
+	'Xi;': 'Ξ',
+	'Xopf;': '𝕏',
+	'Xscr;': '𝒳',
+	'YAcy;': 'Я',
+	'YIcy;': 'Ї',
+	'YUcy;': 'Ю',
+	Yacute: 'Ý',
+	'Yacute;': 'Ý',
+	'Ycirc;': 'Ŷ',
+	'Ycy;': 'Ы',
+	'Yfr;': '𝔜',
+	'Yopf;': '𝕐',
+	'Yscr;': '𝒴',
+	'Yuml;': 'Ÿ',
+	'ZHcy;': 'Ж',
+	'Zacute;': 'Ź',
+	'Zcaron;': 'Ž',
+	'Zcy;': 'З',
+	'Zdot;': 'Ż',
+	'ZeroWidthSpace;': '​',
+	'Zeta;': 'Ζ',
+	'Zfr;': 'ℨ',
+	'Zopf;': 'ℤ',
+	'Zscr;': '𝒵',
+	aacute: 'á',
+	'aacute;': 'á',
+	'abreve;': 'ă',
+	'ac;': '∾',
+	'acE;': '∾̳',
+	'acd;': '∿',
+	acirc: 'â',
+	'acirc;': 'â',
+	acute: '´',
+	'acute;': '´',
+	'acy;': 'а',
+	aelig: 'æ',
+	'aelig;': 'æ',
+	'af;': '⁡',
+	'afr;': '𝔞',
+	agrave: 'à',
+	'agrave;': 'à',
+	'alefsym;': 'ℵ',
+	'aleph;': 'ℵ',
+	'alpha;': 'α',
+	'amacr;': 'ā',
+	'amalg;': '⨿',
+	amp: '&',
+	'amp;': '&',
+	'and;': '∧',
+	'andand;': '⩕',
+	'andd;': '⩜',
+	'andslope;': '⩘',
+	'andv;': '⩚',
+	'ang;': '∠',
+	'ange;': '⦤',
+	'angle;': '∠',
+	'angmsd;': '∡',
+	'angmsdaa;': '⦨',
+	'angmsdab;': '⦩',
+	'angmsdac;': '⦪',
+	'angmsdad;': '⦫',
+	'angmsdae;': '⦬',
+	'angmsdaf;': '⦭',
+	'angmsdag;': '⦮',
+	'angmsdah;': '⦯',
+	'angrt;': '∟',
+	'angrtvb;': '⊾',
+	'angrtvbd;': '⦝',
+	'angsph;': '∢',
+	'angst;': 'Å',
+	'angzarr;': '⍼',
+	'aogon;': 'ą',
+	'aopf;': '𝕒',
+	'ap;': '≈',
+	'apE;': '⩰',
+	'apacir;': '⩯',
+	'ape;': '≊',
+	'apid;': '≋',
+	'apos;': "'",
+	'approx;': '≈',
+	'approxeq;': '≊',
+	aring: 'å',
+	'aring;': 'å',
+	'ascr;': '𝒶',
+	'ast;': '*',
+	'asymp;': '≈',
+	'asympeq;': '≍',
+	atilde: 'ã',
+	'atilde;': 'ã',
+	auml: 'ä',
+	'auml;': 'ä',
+	'awconint;': '∳',
+	'awint;': '⨑',
+	'bNot;': '⫭',
+	'backcong;': '≌',
+	'backepsilon;': '϶',
+	'backprime;': '‵',
+	'backsim;': '∽',
+	'backsimeq;': '⋍',
+	'barvee;': '⊽',
+	'barwed;': '⌅',
+	'barwedge;': '⌅',
+	'bbrk;': '⎵',
+	'bbrktbrk;': '⎶',
+	'bcong;': '≌',
+	'bcy;': 'б',
+	'bdquo;': '„',
+	'becaus;': '∵',
+	'because;': '∵',
+	'bemptyv;': '⦰',
+	'bepsi;': '϶',
+	'bernou;': 'ℬ',
+	'beta;': 'β',
+	'beth;': 'ℶ',
+	'between;': '≬',
+	'bfr;': '𝔟',
+	'bigcap;': '⋂',
+	'bigcirc;': '◯',
+	'bigcup;': '⋃',
+	'bigodot;': '⨀',
+	'bigoplus;': '⨁',
+	'bigotimes;': '⨂',
+	'bigsqcup;': '⨆',
+	'bigstar;': '★',
+	'bigtriangledown;': '▽',
+	'bigtriangleup;': '△',
+	'biguplus;': '⨄',
+	'bigvee;': '⋁',
+	'bigwedge;': '⋀',
+	'bkarow;': '⤍',
+	'blacklozenge;': '⧫',
+	'blacksquare;': '▪',
+	'blacktriangle;': '▴',
+	'blacktriangledown;': '▾',
+	'blacktriangleleft;': '◂',
+	'blacktriangleright;': '▸',
+	'blank;': '␣',
+	'blk12;': '▒',
+	'blk14;': '░',
+	'blk34;': '▓',
+	'block;': '█',
+	'bne;': '=⃥',
+	'bnequiv;': '≡⃥',
+	'bnot;': '⌐',
+	'bopf;': '𝕓',
+	'bot;': '⊥',
+	'bottom;': '⊥',
+	'bowtie;': '⋈',
+	'boxDL;': '╗',
+	'boxDR;': '╔',
+	'boxDl;': '╖',
+	'boxDr;': '╓',
+	'boxH;': '═',
+	'boxHD;': '╦',
+	'boxHU;': '╩',
+	'boxHd;': '╤',
+	'boxHu;': '╧',
+	'boxUL;': '╝',
+	'boxUR;': '╚',
+	'boxUl;': '╜',
+	'boxUr;': '╙',
+	'boxV;': '║',
+	'boxVH;': '╬',
+	'boxVL;': '╣',
+	'boxVR;': '╠',
+	'boxVh;': '╫',
+	'boxVl;': '╢',
+	'boxVr;': '╟',
+	'boxbox;': '⧉',
+	'boxdL;': '╕',
+	'boxdR;': '╒',
+	'boxdl;': '┐',
+	'boxdr;': '┌',
+	'boxh;': '─',
+	'boxhD;': '╥',
+	'boxhU;': '╨',
+	'boxhd;': '┬',
+	'boxhu;': '┴',
+	'boxminus;': '⊟',
+	'boxplus;': '⊞',
+	'boxtimes;': '⊠',
+	'boxuL;': '╛',
+	'boxuR;': '╘',
+	'boxul;': '┘',
+	'boxur;': '└',
+	'boxv;': '│',
+	'boxvH;': '╪',
+	'boxvL;': '╡',
+	'boxvR;': '╞',
+	'boxvh;': '┼',
+	'boxvl;': '┤',
+	'boxvr;': '├',
+	'bprime;': '‵',
+	'breve;': '˘',
+	brvbar: '¦',
+	'brvbar;': '¦',
+	'bscr;': '𝒷',
+	'bsemi;': '⁏',
+	'bsim;': '∽',
+	'bsime;': '⋍',
+	'bsol;': '\\',
+	'bsolb;': '⧅',
+	'bsolhsub;': '⟈',
+	'bull;': '•',
+	'bullet;': '•',
+	'bump;': '≎',
+	'bumpE;': '⪮',
+	'bumpe;': '≏',
+	'bumpeq;': '≏',
+	'cacute;': 'ć',
+	'cap;': '∩',
+	'capand;': '⩄',
+	'capbrcup;': '⩉',
+	'capcap;': '⩋',
+	'capcup;': '⩇',
+	'capdot;': '⩀',
+	'caps;': '∩︀',
+	'caret;': '⁁',
+	'caron;': 'ˇ',
+	'ccaps;': '⩍',
+	'ccaron;': 'č',
+	ccedil: 'ç',
+	'ccedil;': 'ç',
+	'ccirc;': 'ĉ',
+	'ccups;': '⩌',
+	'ccupssm;': '⩐',
+	'cdot;': 'ċ',
+	cedil: '¸',
+	'cedil;': '¸',
+	'cemptyv;': '⦲',
+	cent: '¢',
+	'cent;': '¢',
+	'centerdot;': '·',
+	'cfr;': '𝔠',
+	'chcy;': 'ч',
+	'check;': '✓',
+	'checkmark;': '✓',
+	'chi;': 'χ',
+	'cir;': '○',
+	'cirE;': '⧃',
+	'circ;': 'ˆ',
+	'circeq;': '≗',
+	'circlearrowleft;': '↺',
+	'circlearrowright;': '↻',
+	'circledR;': '®',
+	'circledS;': 'Ⓢ',
+	'circledast;': '⊛',
+	'circledcirc;': '⊚',
+	'circleddash;': '⊝',
+	'cire;': '≗',
+	'cirfnint;': '⨐',
+	'cirmid;': '⫯',
+	'cirscir;': '⧂',
+	'clubs;': '♣',
+	'clubsuit;': '♣',
+	'colon;': ':',
+	'colone;': '≔',
+	'coloneq;': '≔',
+	'comma;': ',',
+	'commat;': '@',
+	'comp;': '∁',
+	'compfn;': '∘',
+	'complement;': '∁',
+	'complexes;': 'ℂ',
+	'cong;': '≅',
+	'congdot;': '⩭',
+	'conint;': '∮',
+	'copf;': '𝕔',
+	'coprod;': '∐',
+	copy: '©',
+	'copy;': '©',
+	'copysr;': '℗',
+	'crarr;': '↵',
+	'cross;': '✗',
+	'cscr;': '𝒸',
+	'csub;': '⫏',
+	'csube;': '⫑',
+	'csup;': '⫐',
+	'csupe;': '⫒',
+	'ctdot;': '⋯',
+	'cudarrl;': '⤸',
+	'cudarrr;': '⤵',
+	'cuepr;': '⋞',
+	'cuesc;': '⋟',
+	'cularr;': '↶',
+	'cularrp;': '⤽',
+	'cup;': '∪',
+	'cupbrcap;': '⩈',
+	'cupcap;': '⩆',
+	'cupcup;': '⩊',
+	'cupdot;': '⊍',
+	'cupor;': '⩅',
+	'cups;': '∪︀',
+	'curarr;': '↷',
+	'curarrm;': '⤼',
+	'curlyeqprec;': '⋞',
+	'curlyeqsucc;': '⋟',
+	'curlyvee;': '⋎',
+	'curlywedge;': '⋏',
+	curren: '¤',
+	'curren;': '¤',
+	'curvearrowleft;': '↶',
+	'curvearrowright;': '↷',
+	'cuvee;': '⋎',
+	'cuwed;': '⋏',
+	'cwconint;': '∲',
+	'cwint;': '∱',
+	'cylcty;': '⌭',
+	'dArr;': '⇓',
+	'dHar;': '⥥',
+	'dagger;': '†',
+	'daleth;': 'ℸ',
+	'darr;': '↓',
+	'dash;': '‐',
+	'dashv;': '⊣',
+	'dbkarow;': '⤏',
+	'dblac;': '˝',
+	'dcaron;': 'ď',
+	'dcy;': 'д',
+	'dd;': 'ⅆ',
+	'ddagger;': '‡',
+	'ddarr;': '⇊',
+	'ddotseq;': '⩷',
+	deg: '°',
+	'deg;': '°',
+	'delta;': 'δ',
+	'demptyv;': '⦱',
+	'dfisht;': '⥿',
+	'dfr;': '𝔡',
+	'dharl;': '⇃',
+	'dharr;': '⇂',
+	'diam;': '⋄',
+	'diamond;': '⋄',
+	'diamondsuit;': '♦',
+	'diams;': '♦',
+	'die;': '¨',
+	'digamma;': 'ϝ',
+	'disin;': '⋲',
+	'div;': '÷',
+	divide: '÷',
+	'divide;': '÷',
+	'divideontimes;': '⋇',
+	'divonx;': '⋇',
+	'djcy;': 'ђ',
+	'dlcorn;': '⌞',
+	'dlcrop;': '⌍',
+	'dollar;': '$',
+	'dopf;': '𝕕',
+	'dot;': '˙',
+	'doteq;': '≐',
+	'doteqdot;': '≑',
+	'dotminus;': '∸',
+	'dotplus;': '∔',
+	'dotsquare;': '⊡',
+	'doublebarwedge;': '⌆',
+	'downarrow;': '↓',
+	'downdownarrows;': '⇊',
+	'downharpoonleft;': '⇃',
+	'downharpoonright;': '⇂',
+	'drbkarow;': '⤐',
+	'drcorn;': '⌟',
+	'drcrop;': '⌌',
+	'dscr;': '𝒹',
+	'dscy;': 'ѕ',
+	'dsol;': '⧶',
+	'dstrok;': 'đ',
+	'dtdot;': '⋱',
+	'dtri;': '▿',
+	'dtrif;': '▾',
+	'duarr;': '⇵',
+	'duhar;': '⥯',
+	'dwangle;': '⦦',
+	'dzcy;': 'џ',
+	'dzigrarr;': '⟿',
+	'eDDot;': '⩷',
+	'eDot;': '≑',
+	eacute: 'é',
+	'eacute;': 'é',
+	'easter;': '⩮',
+	'ecaron;': 'ě',
+	'ecir;': '≖',
+	ecirc: 'ê',
+	'ecirc;': 'ê',
+	'ecolon;': '≕',
+	'ecy;': 'э',
+	'edot;': 'ė',
+	'ee;': 'ⅇ',
+	'efDot;': '≒',
+	'efr;': '𝔢',
+	'eg;': '⪚',
+	egrave: 'è',
+	'egrave;': 'è',
+	'egs;': '⪖',
+	'egsdot;': '⪘',
+	'el;': '⪙',
+	'elinters;': '⏧',
+	'ell;': 'ℓ',
+	'els;': '⪕',
+	'elsdot;': '⪗',
+	'emacr;': 'ē',
+	'empty;': '∅',
+	'emptyset;': '∅',
+	'emptyv;': '∅',
+	'emsp13;': ' ',
+	'emsp14;': ' ',
+	'emsp;': ' ',
+	'eng;': 'ŋ',
+	'ensp;': ' ',
+	'eogon;': 'ę',
+	'eopf;': '𝕖',
+	'epar;': '⋕',
+	'eparsl;': '⧣',
+	'eplus;': '⩱',
+	'epsi;': 'ε',
+	'epsilon;': 'ε',
+	'epsiv;': 'ϵ',
+	'eqcirc;': '≖',
+	'eqcolon;': '≕',
+	'eqsim;': '≂',
+	'eqslantgtr;': '⪖',
+	'eqslantless;': '⪕',
+	'equals;': '=',
+	'equest;': '≟',
+	'equiv;': '≡',
+	'equivDD;': '⩸',
+	'eqvparsl;': '⧥',
+	'erDot;': '≓',
+	'erarr;': '⥱',
+	'escr;': 'ℯ',
+	'esdot;': '≐',
+	'esim;': '≂',
+	'eta;': 'η',
+	eth: 'ð',
+	'eth;': 'ð',
+	euml: 'ë',
+	'euml;': 'ë',
+	'euro;': '€',
+	'excl;': '!',
+	'exist;': '∃',
+	'expectation;': 'ℰ',
+	'exponentiale;': 'ⅇ',
+	'fallingdotseq;': '≒',
+	'fcy;': 'ф',
+	'female;': '♀',
+	'ffilig;': 'ﬃ',
+	'fflig;': 'ﬀ',
+	'ffllig;': 'ﬄ',
+	'ffr;': '𝔣',
+	'filig;': 'ﬁ',
+	'fjlig;': 'fj',
+	'flat;': '♭',
+	'fllig;': 'ﬂ',
+	'fltns;': '▱',
+	'fnof;': 'ƒ',
+	'fopf;': '𝕗',
+	'forall;': '∀',
+	'fork;': '⋔',
+	'forkv;': '⫙',
+	'fpartint;': '⨍',
+	frac12: '½',
+	'frac12;': '½',
+	'frac13;': '⅓',
+	frac14: '¼',
+	'frac14;': '¼',
+	'frac15;': '⅕',
+	'frac16;': '⅙',
+	'frac18;': '⅛',
+	'frac23;': '⅔',
+	'frac25;': '⅖',
+	frac34: '¾',
+	'frac34;': '¾',
+	'frac35;': '⅗',
+	'frac38;': '⅜',
+	'frac45;': '⅘',
+	'frac56;': '⅚',
+	'frac58;': '⅝',
+	'frac78;': '⅞',
+	'frasl;': '⁄',
+	'frown;': '⌢',
+	'fscr;': '𝒻',
+	'gE;': '≧',
+	'gEl;': '⪌',
+	'gacute;': 'ǵ',
+	'gamma;': 'γ',
+	'gammad;': 'ϝ',
+	'gap;': '⪆',
+	'gbreve;': 'ğ',
+	'gcirc;': 'ĝ',
+	'gcy;': 'г',
+	'gdot;': 'ġ',
+	'ge;': '≥',
+	'gel;': '⋛',
+	'geq;': '≥',
+	'geqq;': '≧',
+	'geqslant;': '⩾',
+	'ges;': '⩾',
+	'gescc;': '⪩',
+	'gesdot;': '⪀',
+	'gesdoto;': '⪂',
+	'gesdotol;': '⪄',
+	'gesl;': '⋛︀',
+	'gesles;': '⪔',
+	'gfr;': '𝔤',
+	'gg;': '≫',
+	'ggg;': '⋙',
+	'gimel;': 'ℷ',
+	'gjcy;': 'ѓ',
+	'gl;': '≷',
+	'glE;': '⪒',
+	'gla;': '⪥',
+	'glj;': '⪤',
+	'gnE;': '≩',
+	'gnap;': '⪊',
+	'gnapprox;': '⪊',
+	'gne;': '⪈',
+	'gneq;': '⪈',
+	'gneqq;': '≩',
+	'gnsim;': '⋧',
+	'gopf;': '𝕘',
+	'grave;': '`',
+	'gscr;': 'ℊ',
+	'gsim;': '≳',
+	'gsime;': '⪎',
+	'gsiml;': '⪐',
+	gt: '>',
+	'gt;': '>',
+	'gtcc;': '⪧',
+	'gtcir;': '⩺',
+	'gtdot;': '⋗',
+	'gtlPar;': '⦕',
+	'gtquest;': '⩼',
+	'gtrapprox;': '⪆',
+	'gtrarr;': '⥸',
+	'gtrdot;': '⋗',
+	'gtreqless;': '⋛',
+	'gtreqqless;': '⪌',
+	'gtrless;': '≷',
+	'gtrsim;': '≳',
+	'gvertneqq;': '≩︀',
+	'gvnE;': '≩︀',
+	'hArr;': '⇔',
+	'hairsp;': ' ',
+	'half;': '½',
+	'hamilt;': 'ℋ',
+	'hardcy;': 'ъ',
+	'harr;': '↔',
+	'harrcir;': '⥈',
+	'harrw;': '↭',
+	'hbar;': 'ℏ',
+	'hcirc;': 'ĥ',
+	'hearts;': '♥',
+	'heartsuit;': '♥',
+	'hellip;': '…',
+	'hercon;': '⊹',
+	'hfr;': '𝔥',
+	'hksearow;': '⤥',
+	'hkswarow;': '⤦',
+	'hoarr;': '⇿',
+	'homtht;': '∻',
+	'hookleftarrow;': '↩',
+	'hookrightarrow;': '↪',
+	'hopf;': '𝕙',
+	'horbar;': '―',
+	'hscr;': '𝒽',
+	'hslash;': 'ℏ',
+	'hstrok;': 'ħ',
+	'hybull;': '⁃',
+	'hyphen;': '‐',
+	iacute: 'í',
+	'iacute;': 'í',
+	'ic;': '⁣',
+	icirc: 'î',
+	'icirc;': 'î',
+	'icy;': 'и',
+	'iecy;': 'е',
+	iexcl: '¡',
+	'iexcl;': '¡',
+	'iff;': '⇔',
+	'ifr;': '𝔦',
+	igrave: 'ì',
+	'igrave;': 'ì',
+	'ii;': 'ⅈ',
+	'iiiint;': '⨌',
+	'iiint;': '∭',
+	'iinfin;': '⧜',
+	'iiota;': '℩',
+	'ijlig;': 'ĳ',
+	'imacr;': 'ī',
+	'image;': 'ℑ',
+	'imagline;': 'ℐ',
+	'imagpart;': 'ℑ',
+	'imath;': 'ı',
+	'imof;': '⊷',
+	'imped;': 'Ƶ',
+	'in;': '∈',
+	'incare;': '℅',
+	'infin;': '∞',
+	'infintie;': '⧝',
+	'inodot;': 'ı',
+	'int;': '∫',
+	'intcal;': '⊺',
+	'integers;': 'ℤ',
+	'intercal;': '⊺',
+	'intlarhk;': '⨗',
+	'intprod;': '⨼',
+	'iocy;': 'ё',
+	'iogon;': 'į',
+	'iopf;': '𝕚',
+	'iota;': 'ι',
+	'iprod;': '⨼',
+	iquest: '¿',
+	'iquest;': '¿',
+	'iscr;': '𝒾',
+	'isin;': '∈',
+	'isinE;': '⋹',
+	'isindot;': '⋵',
+	'isins;': '⋴',
+	'isinsv;': '⋳',
+	'isinv;': '∈',
+	'it;': '⁢',
+	'itilde;': 'ĩ',
+	'iukcy;': 'і',
+	iuml: 'ï',
+	'iuml;': 'ï',
+	'jcirc;': 'ĵ',
+	'jcy;': 'й',
+	'jfr;': '𝔧',
+	'jmath;': 'ȷ',
+	'jopf;': '𝕛',
+	'jscr;': '𝒿',
+	'jsercy;': 'ј',
+	'jukcy;': 'є',
+	'kappa;': 'κ',
+	'kappav;': 'ϰ',
+	'kcedil;': 'ķ',
+	'kcy;': 'к',
+	'kfr;': '𝔨',
+	'kgreen;': 'ĸ',
+	'khcy;': 'х',
+	'kjcy;': 'ќ',
+	'kopf;': '𝕜',
+	'kscr;': '𝓀',
+	'lAarr;': '⇚',
+	'lArr;': '⇐',
+	'lAtail;': '⤛',
+	'lBarr;': '⤎',
+	'lE;': '≦',
+	'lEg;': '⪋',
+	'lHar;': '⥢',
+	'lacute;': 'ĺ',
+	'laemptyv;': '⦴',
+	'lagran;': 'ℒ',
+	'lambda;': 'λ',
+	'lang;': '⟨',
+	'langd;': '⦑',
+	'langle;': '⟨',
+	'lap;': '⪅',
+	laquo: '«',
+	'laquo;': '«',
+	'larr;': '←',
+	'larrb;': '⇤',
+	'larrbfs;': '⤟',
+	'larrfs;': '⤝',
+	'larrhk;': '↩',
+	'larrlp;': '↫',
+	'larrpl;': '⤹',
+	'larrsim;': '⥳',
+	'larrtl;': '↢',
+	'lat;': '⪫',
+	'latail;': '⤙',
+	'late;': '⪭',
+	'lates;': '⪭︀',
+	'lbarr;': '⤌',
+	'lbbrk;': '❲',
+	'lbrace;': '{',
+	'lbrack;': '[',
+	'lbrke;': '⦋',
+	'lbrksld;': '⦏',
+	'lbrkslu;': '⦍',
+	'lcaron;': 'ľ',
+	'lcedil;': 'ļ',
+	'lceil;': '⌈',
+	'lcub;': '{',
+	'lcy;': 'л',
+	'ldca;': '⤶',
+	'ldquo;': '“',
+	'ldquor;': '„',
+	'ldrdhar;': '⥧',
+	'ldrushar;': '⥋',
+	'ldsh;': '↲',
+	'le;': '≤',
+	'leftarrow;': '←',
+	'leftarrowtail;': '↢',
+	'leftharpoondown;': '↽',
+	'leftharpoonup;': '↼',
+	'leftleftarrows;': '⇇',
+	'leftrightarrow;': '↔',
+	'leftrightarrows;': '⇆',
+	'leftrightharpoons;': '⇋',
+	'leftrightsquigarrow;': '↭',
+	'leftthreetimes;': '⋋',
+	'leg;': '⋚',
+	'leq;': '≤',
+	'leqq;': '≦',
+	'leqslant;': '⩽',
+	'les;': '⩽',
+	'lescc;': '⪨',
+	'lesdot;': '⩿',
+	'lesdoto;': '⪁',
+	'lesdotor;': '⪃',
+	'lesg;': '⋚︀',
+	'lesges;': '⪓',
+	'lessapprox;': '⪅',
+	'lessdot;': '⋖',
+	'lesseqgtr;': '⋚',
+	'lesseqqgtr;': '⪋',
+	'lessgtr;': '≶',
+	'lesssim;': '≲',
+	'lfisht;': '⥼',
+	'lfloor;': '⌊',
+	'lfr;': '𝔩',
+	'lg;': '≶',
+	'lgE;': '⪑',
+	'lhard;': '↽',
+	'lharu;': '↼',
+	'lharul;': '⥪',
+	'lhblk;': '▄',
+	'ljcy;': 'љ',
+	'll;': '≪',
+	'llarr;': '⇇',
+	'llcorner;': '⌞',
+	'llhard;': '⥫',
+	'lltri;': '◺',
+	'lmidot;': 'ŀ',
+	'lmoust;': '⎰',
+	'lmoustache;': '⎰',
+	'lnE;': '≨',
+	'lnap;': '⪉',
+	'lnapprox;': '⪉',
+	'lne;': '⪇',
+	'lneq;': '⪇',
+	'lneqq;': '≨',
+	'lnsim;': '⋦',
+	'loang;': '⟬',
+	'loarr;': '⇽',
+	'lobrk;': '⟦',
+	'longleftarrow;': '⟵',
+	'longleftrightarrow;': '⟷',
+	'longmapsto;': '⟼',
+	'longrightarrow;': '⟶',
+	'looparrowleft;': '↫',
+	'looparrowright;': '↬',
+	'lopar;': '⦅',
+	'lopf;': '𝕝',
+	'loplus;': '⨭',
+	'lotimes;': '⨴',
+	'lowast;': '∗',
+	'lowbar;': '_',
+	'loz;': '◊',
+	'lozenge;': '◊',
+	'lozf;': '⧫',
+	'lpar;': '(',
+	'lparlt;': '⦓',
+	'lrarr;': '⇆',
+	'lrcorner;': '⌟',
+	'lrhar;': '⇋',
+	'lrhard;': '⥭',
+	'lrm;': '‎',
+	'lrtri;': '⊿',
+	'lsaquo;': '‹',
+	'lscr;': '𝓁',
+	'lsh;': '↰',
+	'lsim;': '≲',
+	'lsime;': '⪍',
+	'lsimg;': '⪏',
+	'lsqb;': '[',
+	'lsquo;': '‘',
+	'lsquor;': '‚',
+	'lstrok;': 'ł',
+	lt: '<',
+	'lt;': '<',
+	'ltcc;': '⪦',
+	'ltcir;': '⩹',
+	'ltdot;': '⋖',
+	'lthree;': '⋋',
+	'ltimes;': '⋉',
+	'ltlarr;': '⥶',
+	'ltquest;': '⩻',
+	'ltrPar;': '⦖',
+	'ltri;': '◃',
+	'ltrie;': '⊴',
+	'ltrif;': '◂',
+	'lurdshar;': '⥊',
+	'luruhar;': '⥦',
+	'lvertneqq;': '≨︀',
+	'lvnE;': '≨︀',
+	'mDDot;': '∺',
+	macr: '¯',
+	'macr;': '¯',
+	'male;': '♂',
+	'malt;': '✠',
+	'maltese;': '✠',
+	'map;': '↦',
+	'mapsto;': '↦',
+	'mapstodown;': '↧',
+	'mapstoleft;': '↤',
+	'mapstoup;': '↥',
+	'marker;': '▮',
+	'mcomma;': '⨩',
+	'mcy;': 'м',
+	'mdash;': '—',
+	'measuredangle;': '∡',
+	'mfr;': '𝔪',
+	'mho;': '℧',
+	micro: 'µ',
+	'micro;': 'µ',
+	'mid;': '∣',
+	'midast;': '*',
+	'midcir;': '⫰',
+	middot: '·',
+	'middot;': '·',
+	'minus;': '−',
+	'minusb;': '⊟',
+	'minusd;': '∸',
+	'minusdu;': '⨪',
+	'mlcp;': '⫛',
+	'mldr;': '…',
+	'mnplus;': '∓',
+	'models;': '⊧',
+	'mopf;': '𝕞',
+	'mp;': '∓',
+	'mscr;': '𝓂',
+	'mstpos;': '∾',
+	'mu;': 'μ',
+	'multimap;': '⊸',
+	'mumap;': '⊸',
+	'nGg;': '⋙̸',
+	'nGt;': '≫⃒',
+	'nGtv;': '≫̸',
+	'nLeftarrow;': '⇍',
+	'nLeftrightarrow;': '⇎',
+	'nLl;': '⋘̸',
+	'nLt;': '≪⃒',
+	'nLtv;': '≪̸',
+	'nRightarrow;': '⇏',
+	'nVDash;': '⊯',
+	'nVdash;': '⊮',
+	'nabla;': '∇',
+	'nacute;': 'ń',
+	'nang;': '∠⃒',
+	'nap;': '≉',
+	'napE;': '⩰̸',
+	'napid;': '≋̸',
+	'napos;': 'ŉ',
+	'napprox;': '≉',
+	'natur;': '♮',
+	'natural;': '♮',
+	'naturals;': 'ℕ',
+	nbsp: ' ',
+	'nbsp;': ' ',
+	'nbump;': '≎̸',
+	'nbumpe;': '≏̸',
+	'ncap;': '⩃',
+	'ncaron;': 'ň',
+	'ncedil;': 'ņ',
+	'ncong;': '≇',
+	'ncongdot;': '⩭̸',
+	'ncup;': '⩂',
+	'ncy;': 'н',
+	'ndash;': '–',
+	'ne;': '≠',
+	'neArr;': '⇗',
+	'nearhk;': '⤤',
+	'nearr;': '↗',
+	'nearrow;': '↗',
+	'nedot;': '≐̸',
+	'nequiv;': '≢',
+	'nesear;': '⤨',
+	'nesim;': '≂̸',
+	'nexist;': '∄',
+	'nexists;': '∄',
+	'nfr;': '𝔫',
+	'ngE;': '≧̸',
+	'nge;': '≱',
+	'ngeq;': '≱',
+	'ngeqq;': '≧̸',
+	'ngeqslant;': '⩾̸',
+	'nges;': '⩾̸',
+	'ngsim;': '≵',
+	'ngt;': '≯',
+	'ngtr;': '≯',
+	'nhArr;': '⇎',
+	'nharr;': '↮',
+	'nhpar;': '⫲',
+	'ni;': '∋',
+	'nis;': '⋼',
+	'nisd;': '⋺',
+	'niv;': '∋',
+	'njcy;': 'њ',
+	'nlArr;': '⇍',
+	'nlE;': '≦̸',
+	'nlarr;': '↚',
+	'nldr;': '‥',
+	'nle;': '≰',
+	'nleftarrow;': '↚',
+	'nleftrightarrow;': '↮',
+	'nleq;': '≰',
+	'nleqq;': '≦̸',
+	'nleqslant;': '⩽̸',
+	'nles;': '⩽̸',
+	'nless;': '≮',
+	'nlsim;': '≴',
+	'nlt;': '≮',
+	'nltri;': '⋪',
+	'nltrie;': '⋬',
+	'nmid;': '∤',
+	'nopf;': '𝕟',
+	not: '¬',
+	'not;': '¬',
+	'notin;': '∉',
+	'notinE;': '⋹̸',
+	'notindot;': '⋵̸',
+	'notinva;': '∉',
+	'notinvb;': '⋷',
+	'notinvc;': '⋶',
+	'notni;': '∌',
+	'notniva;': '∌',
+	'notnivb;': '⋾',
+	'notnivc;': '⋽',
+	'npar;': '∦',
+	'nparallel;': '∦',
+	'nparsl;': '⫽⃥',
+	'npart;': '∂̸',
+	'npolint;': '⨔',
+	'npr;': '⊀',
+	'nprcue;': '⋠',
+	'npre;': '⪯̸',
+	'nprec;': '⊀',
+	'npreceq;': '⪯̸',
+	'nrArr;': '⇏',
+	'nrarr;': '↛',
+	'nrarrc;': '⤳̸',
+	'nrarrw;': '↝̸',
+	'nrightarrow;': '↛',
+	'nrtri;': '⋫',
+	'nrtrie;': '⋭',
+	'nsc;': '⊁',
+	'nsccue;': '⋡',
+	'nsce;': '⪰̸',
+	'nscr;': '𝓃',
+	'nshortmid;': '∤',
+	'nshortparallel;': '∦',
+	'nsim;': '≁',
+	'nsime;': '≄',
+	'nsimeq;': '≄',
+	'nsmid;': '∤',
+	'nspar;': '∦',
+	'nsqsube;': '⋢',
+	'nsqsupe;': '⋣',
+	'nsub;': '⊄',
+	'nsubE;': '⫅̸',
+	'nsube;': '⊈',
+	'nsubset;': '⊂⃒',
+	'nsubseteq;': '⊈',
+	'nsubseteqq;': '⫅̸',
+	'nsucc;': '⊁',
+	'nsucceq;': '⪰̸',
+	'nsup;': '⊅',
+	'nsupE;': '⫆̸',
+	'nsupe;': '⊉',
+	'nsupset;': '⊃⃒',
+	'nsupseteq;': '⊉',
+	'nsupseteqq;': '⫆̸',
+	'ntgl;': '≹',
+	ntilde: 'ñ',
+	'ntilde;': 'ñ',
+	'ntlg;': '≸',
+	'ntriangleleft;': '⋪',
+	'ntrianglelefteq;': '⋬',
+	'ntriangleright;': '⋫',
+	'ntrianglerighteq;': '⋭',
+	'nu;': 'ν',
+	'num;': '#',
+	'numero;': '№',
+	'numsp;': ' ',
+	'nvDash;': '⊭',
+	'nvHarr;': '⤄',
+	'nvap;': '≍⃒',
+	'nvdash;': '⊬',
+	'nvge;': '≥⃒',
+	'nvgt;': '>⃒',
+	'nvinfin;': '⧞',
+	'nvlArr;': '⤂',
+	'nvle;': '≤⃒',
+	'nvlt;': '<⃒',
+	'nvltrie;': '⊴⃒',
+	'nvrArr;': '⤃',
+	'nvrtrie;': '⊵⃒',
+	'nvsim;': '∼⃒',
+	'nwArr;': '⇖',
+	'nwarhk;': '⤣',
+	'nwarr;': '↖',
+	'nwarrow;': '↖',
+	'nwnear;': '⤧',
+	'oS;': 'Ⓢ',
+	oacute: 'ó',
+	'oacute;': 'ó',
+	'oast;': '⊛',
+	'ocir;': '⊚',
+	ocirc: 'ô',
+	'ocirc;': 'ô',
+	'ocy;': 'о',
+	'odash;': '⊝',
+	'odblac;': 'ő',
+	'odiv;': '⨸',
+	'odot;': '⊙',
+	'odsold;': '⦼',
+	'oelig;': 'œ',
+	'ofcir;': '⦿',
+	'ofr;': '𝔬',
+	'ogon;': '˛',
+	ograve: 'ò',
+	'ograve;': 'ò',
+	'ogt;': '⧁',
+	'ohbar;': '⦵',
+	'ohm;': 'Ω',
+	'oint;': '∮',
+	'olarr;': '↺',
+	'olcir;': '⦾',
+	'olcross;': '⦻',
+	'oline;': '‾',
+	'olt;': '⧀',
+	'omacr;': 'ō',
+	'omega;': 'ω',
+	'omicron;': 'ο',
+	'omid;': '⦶',
+	'ominus;': '⊖',
+	'oopf;': '𝕠',
+	'opar;': '⦷',
+	'operp;': '⦹',
+	'oplus;': '⊕',
+	'or;': '∨',
+	'orarr;': '↻',
+	'ord;': '⩝',
+	'order;': 'ℴ',
+	'orderof;': 'ℴ',
+	ordf: 'ª',
+	'ordf;': 'ª',
+	ordm: 'º',
+	'ordm;': 'º',
+	'origof;': '⊶',
+	'oror;': '⩖',
+	'orslope;': '⩗',
+	'orv;': '⩛',
+	'oscr;': 'ℴ',
+	oslash: 'ø',
+	'oslash;': 'ø',
+	'osol;': '⊘',
+	otilde: 'õ',
+	'otilde;': 'õ',
+	'otimes;': '⊗',
+	'otimesas;': '⨶',
+	ouml: 'ö',
+	'ouml;': 'ö',
+	'ovbar;': '⌽',
+	'par;': '∥',
+	para: '¶',
+	'para;': '¶',
+	'parallel;': '∥',
+	'parsim;': '⫳',
+	'parsl;': '⫽',
+	'part;': '∂',
+	'pcy;': 'п',
+	'percnt;': '%',
+	'period;': '.',
+	'permil;': '‰',
+	'perp;': '⊥',
+	'pertenk;': '‱',
+	'pfr;': '𝔭',
+	'phi;': 'φ',
+	'phiv;': 'ϕ',
+	'phmmat;': 'ℳ',
+	'phone;': '☎',
+	'pi;': 'π',
+	'pitchfork;': '⋔',
+	'piv;': 'ϖ',
+	'planck;': 'ℏ',
+	'planckh;': 'ℎ',
+	'plankv;': 'ℏ',
+	'plus;': '+',
+	'plusacir;': '⨣',
+	'plusb;': '⊞',
+	'pluscir;': '⨢',
+	'plusdo;': '∔',
+	'plusdu;': '⨥',
+	'pluse;': '⩲',
+	plusmn: '±',
+	'plusmn;': '±',
+	'plussim;': '⨦',
+	'plustwo;': '⨧',
+	'pm;': '±',
+	'pointint;': '⨕',
+	'popf;': '𝕡',
+	pound: '£',
+	'pound;': '£',
+	'pr;': '≺',
+	'prE;': '⪳',
+	'prap;': '⪷',
+	'prcue;': '≼',
+	'pre;': '⪯',
+	'prec;': '≺',
+	'precapprox;': '⪷',
+	'preccurlyeq;': '≼',
+	'preceq;': '⪯',
+	'precnapprox;': '⪹',
+	'precneqq;': '⪵',
+	'precnsim;': '⋨',
+	'precsim;': '≾',
+	'prime;': '′',
+	'primes;': 'ℙ',
+	'prnE;': '⪵',
+	'prnap;': '⪹',
+	'prnsim;': '⋨',
+	'prod;': '∏',
+	'profalar;': '⌮',
+	'profline;': '⌒',
+	'profsurf;': '⌓',
+	'prop;': '∝',
+	'propto;': '∝',
+	'prsim;': '≾',
+	'prurel;': '⊰',
+	'pscr;': '𝓅',
+	'psi;': 'ψ',
+	'puncsp;': ' ',
+	'qfr;': '𝔮',
+	'qint;': '⨌',
+	'qopf;': '𝕢',
+	'qprime;': '⁗',
+	'qscr;': '𝓆',
+	'quaternions;': 'ℍ',
+	'quatint;': '⨖',
+	'quest;': '?',
+	'questeq;': '≟',
+	quot: '"',
+	'quot;': '"',
+	'rAarr;': '⇛',
+	'rArr;': '⇒',
+	'rAtail;': '⤜',
+	'rBarr;': '⤏',
+	'rHar;': '⥤',
+	'race;': '∽̱',
+	'racute;': 'ŕ',
+	'radic;': '√',
+	'raemptyv;': '⦳',
+	'rang;': '⟩',
+	'rangd;': '⦒',
+	'range;': '⦥',
+	'rangle;': '⟩',
+	raquo: '»',
+	'raquo;': '»',
+	'rarr;': '→',
+	'rarrap;': '⥵',
+	'rarrb;': '⇥',
+	'rarrbfs;': '⤠',
+	'rarrc;': '⤳',
+	'rarrfs;': '⤞',
+	'rarrhk;': '↪',
+	'rarrlp;': '↬',
+	'rarrpl;': '⥅',
+	'rarrsim;': '⥴',
+	'rarrtl;': '↣',
+	'rarrw;': '↝',
+	'ratail;': '⤚',
+	'ratio;': '∶',
+	'rationals;': 'ℚ',
+	'rbarr;': '⤍',
+	'rbbrk;': '❳',
+	'rbrace;': '}',
+	'rbrack;': ']',
+	'rbrke;': '⦌',
+	'rbrksld;': '⦎',
+	'rbrkslu;': '⦐',
+	'rcaron;': 'ř',
+	'rcedil;': 'ŗ',
+	'rceil;': '⌉',
+	'rcub;': '}',
+	'rcy;': 'р',
+	'rdca;': '⤷',
+	'rdldhar;': '⥩',
+	'rdquo;': '”',
+	'rdquor;': '”',
+	'rdsh;': '↳',
+	'real;': 'ℜ',
+	'realine;': 'ℛ',
+	'realpart;': 'ℜ',
+	'reals;': 'ℝ',
+	'rect;': '▭',
+	reg: '®',
+	'reg;': '®',
+	'rfisht;': '⥽',
+	'rfloor;': '⌋',
+	'rfr;': '𝔯',
+	'rhard;': '⇁',
+	'rharu;': '⇀',
+	'rharul;': '⥬',
+	'rho;': 'ρ',
+	'rhov;': 'ϱ',
+	'rightarrow;': '→',
+	'rightarrowtail;': '↣',
+	'rightharpoondown;': '⇁',
+	'rightharpoonup;': '⇀',
+	'rightleftarrows;': '⇄',
+	'rightleftharpoons;': '⇌',
+	'rightrightarrows;': '⇉',
+	'rightsquigarrow;': '↝',
+	'rightthreetimes;': '⋌',
+	'ring;': '˚',
+	'risingdotseq;': '≓',
+	'rlarr;': '⇄',
+	'rlhar;': '⇌',
+	'rlm;': '‏',
+	'rmoust;': '⎱',
+	'rmoustache;': '⎱',
+	'rnmid;': '⫮',
+	'roang;': '⟭',
+	'roarr;': '⇾',
+	'robrk;': '⟧',
+	'ropar;': '⦆',
+	'ropf;': '𝕣',
+	'roplus;': '⨮',
+	'rotimes;': '⨵',
+	'rpar;': ')',
+	'rpargt;': '⦔',
+	'rppolint;': '⨒',
+	'rrarr;': '⇉',
+	'rsaquo;': '›',
+	'rscr;': '𝓇',
+	'rsh;': '↱',
+	'rsqb;': ']',
+	'rsquo;': '’',
+	'rsquor;': '’',
+	'rthree;': '⋌',
+	'rtimes;': '⋊',
+	'rtri;': '▹',
+	'rtrie;': '⊵',
+	'rtrif;': '▸',
+	'rtriltri;': '⧎',
+	'ruluhar;': '⥨',
+	'rx;': '℞',
+	'sacute;': 'ś',
+	'sbquo;': '‚',
+	'sc;': '≻',
+	'scE;': '⪴',
+	'scap;': '⪸',
+	'scaron;': 'š',
+	'sccue;': '≽',
+	'sce;': '⪰',
+	'scedil;': 'ş',
+	'scirc;': 'ŝ',
+	'scnE;': '⪶',
+	'scnap;': '⪺',
+	'scnsim;': '⋩',
+	'scpolint;': '⨓',
+	'scsim;': '≿',
+	'scy;': 'с',
+	'sdot;': '⋅',
+	'sdotb;': '⊡',
+	'sdote;': '⩦',
+	'seArr;': '⇘',
+	'searhk;': '⤥',
+	'searr;': '↘',
+	'searrow;': '↘',
+	sect: '§',
+	'sect;': '§',
+	'semi;': ';',
+	'seswar;': '⤩',
+	'setminus;': '∖',
+	'setmn;': '∖',
+	'sext;': '✶',
+	'sfr;': '𝔰',
+	'sfrown;': '⌢',
+	'sharp;': '♯',
+	'shchcy;': 'щ',
+	'shcy;': 'ш',
+	'shortmid;': '∣',
+	'shortparallel;': '∥',
+	shy: '­',
+	'shy;': '­',
+	'sigma;': 'σ',
+	'sigmaf;': 'ς',
+	'sigmav;': 'ς',
+	'sim;': '∼',
+	'simdot;': '⩪',
+	'sime;': '≃',
+	'simeq;': '≃',
+	'simg;': '⪞',
+	'simgE;': '⪠',
+	'siml;': '⪝',
+	'simlE;': '⪟',
+	'simne;': '≆',
+	'simplus;': '⨤',
+	'simrarr;': '⥲',
+	'slarr;': '←',
+	'smallsetminus;': '∖',
+	'smashp;': '⨳',
+	'smeparsl;': '⧤',
+	'smid;': '∣',
+	'smile;': '⌣',
+	'smt;': '⪪',
+	'smte;': '⪬',
+	'smtes;': '⪬︀',
+	'softcy;': 'ь',
+	'sol;': '/',
+	'solb;': '⧄',
+	'solbar;': '⌿',
+	'sopf;': '𝕤',
+	'spades;': '♠',
+	'spadesuit;': '♠',
+	'spar;': '∥',
+	'sqcap;': '⊓',
+	'sqcaps;': '⊓︀',
+	'sqcup;': '⊔',
+	'sqcups;': '⊔︀',
+	'sqsub;': '⊏',
+	'sqsube;': '⊑',
+	'sqsubset;': '⊏',
+	'sqsubseteq;': '⊑',
+	'sqsup;': '⊐',
+	'sqsupe;': '⊒',
+	'sqsupset;': '⊐',
+	'sqsupseteq;': '⊒',
+	'squ;': '□',
+	'square;': '□',
+	'squarf;': '▪',
+	'squf;': '▪',
+	'srarr;': '→',
+	'sscr;': '𝓈',
+	'ssetmn;': '∖',
+	'ssmile;': '⌣',
+	'sstarf;': '⋆',
+	'star;': '☆',
+	'starf;': '★',
+	'straightepsilon;': 'ϵ',
+	'straightphi;': 'ϕ',
+	'strns;': '¯',
+	'sub;': '⊂',
+	'subE;': '⫅',
+	'subdot;': '⪽',
+	'sube;': '⊆',
+	'subedot;': '⫃',
+	'submult;': '⫁',
+	'subnE;': '⫋',
+	'subne;': '⊊',
+	'subplus;': '⪿',
+	'subrarr;': '⥹',
+	'subset;': '⊂',
+	'subseteq;': '⊆',
+	'subseteqq;': '⫅',
+	'subsetneq;': '⊊',
+	'subsetneqq;': '⫋',
+	'subsim;': '⫇',
+	'subsub;': '⫕',
+	'subsup;': '⫓',
+	'succ;': '≻',
+	'succapprox;': '⪸',
+	'succcurlyeq;': '≽',
+	'succeq;': '⪰',
+	'succnapprox;': '⪺',
+	'succneqq;': '⪶',
+	'succnsim;': '⋩',
+	'succsim;': '≿',
+	'sum;': '∑',
+	'sung;': '♪',
+	sup1: '¹',
+	'sup1;': '¹',
+	sup2: '²',
+	'sup2;': '²',
+	sup3: '³',
+	'sup3;': '³',
+	'sup;': '⊃',
+	'supE;': '⫆',
+	'supdot;': '⪾',
+	'supdsub;': '⫘',
+	'supe;': '⊇',
+	'supedot;': '⫄',
+	'suphsol;': '⟉',
+	'suphsub;': '⫗',
+	'suplarr;': '⥻',
+	'supmult;': '⫂',
+	'supnE;': '⫌',
+	'supne;': '⊋',
+	'supplus;': '⫀',
+	'supset;': '⊃',
+	'supseteq;': '⊇',
+	'supseteqq;': '⫆',
+	'supsetneq;': '⊋',
+	'supsetneqq;': '⫌',
+	'supsim;': '⫈',
+	'supsub;': '⫔',
+	'supsup;': '⫖',
+	'swArr;': '⇙',
+	'swarhk;': '⤦',
+	'swarr;': '↙',
+	'swarrow;': '↙',
+	'swnwar;': '⤪',
+	szlig: 'ß',
+	'szlig;': 'ß',
+	'target;': '⌖',
+	'tau;': 'τ',
+	'tbrk;': '⎴',
+	'tcaron;': 'ť',
+	'tcedil;': 'ţ',
+	'tcy;': 'т',
+	'tdot;': '⃛',
+	'telrec;': '⌕',
+	'tfr;': '𝔱',
+	'there4;': '∴',
+	'therefore;': '∴',
+	'theta;': 'θ',
+	'thetasym;': 'ϑ',
+	'thetav;': 'ϑ',
+	'thickapprox;': '≈',
+	'thicksim;': '∼',
+	'thinsp;': ' ',
+	'thkap;': '≈',
+	'thksim;': '∼',
+	thorn: 'þ',
+	'thorn;': 'þ',
+	'tilde;': '˜',
+	times: '×',
+	'times;': '×',
+	'timesb;': '⊠',
+	'timesbar;': '⨱',
+	'timesd;': '⨰',
+	'tint;': '∭',
+	'toea;': '⤨',
+	'top;': '⊤',
+	'topbot;': '⌶',
+	'topcir;': '⫱',
+	'topf;': '𝕥',
+	'topfork;': '⫚',
+	'tosa;': '⤩',
+	'tprime;': '‴',
+	'trade;': '™',
+	'triangle;': '▵',
+	'triangledown;': '▿',
+	'triangleleft;': '◃',
+	'trianglelefteq;': '⊴',
+	'triangleq;': '≜',
+	'triangleright;': '▹',
+	'trianglerighteq;': '⊵',
+	'tridot;': '◬',
+	'trie;': '≜',
+	'triminus;': '⨺',
+	'triplus;': '⨹',
+	'trisb;': '⧍',
+	'tritime;': '⨻',
+	'trpezium;': '⏢',
+	'tscr;': '𝓉',
+	'tscy;': 'ц',
+	'tshcy;': 'ћ',
+	'tstrok;': 'ŧ',
+	'twixt;': '≬',
+	'twoheadleftarrow;': '↞',
+	'twoheadrightarrow;': '↠',
+	'uArr;': '⇑',
+	'uHar;': '⥣',
+	uacute: 'ú',
+	'uacute;': 'ú',
+	'uarr;': '↑',
+	'ubrcy;': 'ў',
+	'ubreve;': 'ŭ',
+	ucirc: 'û',
+	'ucirc;': 'û',
+	'ucy;': 'у',
+	'udarr;': '⇅',
+	'udblac;': 'ű',
+	'udhar;': '⥮',
+	'ufisht;': '⥾',
+	'ufr;': '𝔲',
+	ugrave: 'ù',
+	'ugrave;': 'ù',
+	'uharl;': '↿',
+	'uharr;': '↾',
+	'uhblk;': '▀',
+	'ulcorn;': '⌜',
+	'ulcorner;': '⌜',
+	'ulcrop;': '⌏',
+	'ultri;': '◸',
+	'umacr;': 'ū',
+	uml: '¨',
+	'uml;': '¨',
+	'uogon;': 'ų',
+	'uopf;': '𝕦',
+	'uparrow;': '↑',
+	'updownarrow;': '↕',
+	'upharpoonleft;': '↿',
+	'upharpoonright;': '↾',
+	'uplus;': '⊎',
+	'upsi;': 'υ',
+	'upsih;': 'ϒ',
+	'upsilon;': 'υ',
+	'upuparrows;': '⇈',
+	'urcorn;': '⌝',
+	'urcorner;': '⌝',
+	'urcrop;': '⌎',
+	'uring;': 'ů',
+	'urtri;': '◹',
+	'uscr;': '𝓊',
+	'utdot;': '⋰',
+	'utilde;': 'ũ',
+	'utri;': '▵',
+	'utrif;': '▴',
+	'uuarr;': '⇈',
+	uuml: 'ü',
+	'uuml;': 'ü',
+	'uwangle;': '⦧',
+	'vArr;': '⇕',
+	'vBar;': '⫨',
+	'vBarv;': '⫩',
+	'vDash;': '⊨',
+	'vangrt;': '⦜',
+	'varepsilon;': 'ϵ',
+	'varkappa;': 'ϰ',
+	'varnothing;': '∅',
+	'varphi;': 'ϕ',
+	'varpi;': 'ϖ',
+	'varpropto;': '∝',
+	'varr;': '↕',
+	'varrho;': 'ϱ',
+	'varsigma;': 'ς',
+	'varsubsetneq;': '⊊︀',
+	'varsubsetneqq;': '⫋︀',
+	'varsupsetneq;': '⊋︀',
+	'varsupsetneqq;': '⫌︀',
+	'vartheta;': 'ϑ',
+	'vartriangleleft;': '⊲',
+	'vartriangleright;': '⊳',
+	'vcy;': 'в',
+	'vdash;': '⊢',
+	'vee;': '∨',
+	'veebar;': '⊻',
+	'veeeq;': '≚',
+	'vellip;': '⋮',
+	'verbar;': '|',
+	'vert;': '|',
+	'vfr;': '𝔳',
+	'vltri;': '⊲',
+	'vnsub;': '⊂⃒',
+	'vnsup;': '⊃⃒',
+	'vopf;': '𝕧',
+	'vprop;': '∝',
+	'vrtri;': '⊳',
+	'vscr;': '𝓋',
+	'vsubnE;': '⫋︀',
+	'vsubne;': '⊊︀',
+	'vsupnE;': '⫌︀',
+	'vsupne;': '⊋︀',
+	'vzigzag;': '⦚',
+	'wcirc;': 'ŵ',
+	'wedbar;': '⩟',
+	'wedge;': '∧',
+	'wedgeq;': '≙',
+	'weierp;': '℘',
+	'wfr;': '𝔴',
+	'wopf;': '𝕨',
+	'wp;': '℘',
+	'wr;': '≀',
+	'wreath;': '≀',
+	'wscr;': '𝓌',
+	'xcap;': '⋂',
+	'xcirc;': '◯',
+	'xcup;': '⋃',
+	'xdtri;': '▽',
+	'xfr;': '𝔵',
+	'xhArr;': '⟺',
+	'xharr;': '⟷',
+	'xi;': 'ξ',
+	'xlArr;': '⟸',
+	'xlarr;': '⟵',
+	'xmap;': '⟼',
+	'xnis;': '⋻',
+	'xodot;': '⨀',
+	'xopf;': '𝕩',
+	'xoplus;': '⨁',
+	'xotime;': '⨂',
+	'xrArr;': '⟹',
+	'xrarr;': '⟶',
+	'xscr;': '𝓍',
+	'xsqcup;': '⨆',
+	'xuplus;': '⨄',
+	'xutri;': '△',
+	'xvee;': '⋁',
+	'xwedge;': '⋀',
+	yacute: 'ý',
+	'yacute;': 'ý',
+	'yacy;': 'я',
+	'ycirc;': 'ŷ',
+	'ycy;': 'ы',
+	yen: '¥',
+	'yen;': '¥',
+	'yfr;': '𝔶',
+	'yicy;': 'ї',
+	'yopf;': '𝕪',
+	'yscr;': '𝓎',
+	'yucy;': 'ю',
+	yuml: 'ÿ',
+	'yuml;': 'ÿ',
+	'zacute;': 'ź',
+	'zcaron;': 'ž',
+	'zcy;': 'з',
+	'zdot;': 'ż',
+	'zeetrf;': 'ℨ',
+	'zeta;': 'ζ',
+	'zfr;': '𝔷',
+	'zhcy;': 'ж',
+	'zigrarr;': '⇝',
+	'zopf;': '𝕫',
+	'zscr;': '𝓏',
+	'zwj;': '‍',
+	'zwnj;': '‌'
+};
+
+const numeric = /&#(x)?([0-9a-f]+);/i;
+const named = new RegExp(
+	`&(${Object.keys(entities)
+		.sort((a, b) => b.length - a.length)
+		.join('|')})`,
+	'g'
+);
+
+/** @param {string} str */
+export function decode(str) {
+	return str
+		.replace(numeric, (_match, hex, code) => String.fromCharCode(hex ? parseInt(code, 16) : +code))
+		.replace(named, (_match, entity) => entities[entity]);
+}
diff --git a/packages/kit/src/core/prerender/entities.spec.js b/packages/kit/src/core/prerender/entities.spec.js
new file mode 100644
index 000000000..92b63827c
--- /dev/null
+++ b/packages/kit/src/core/prerender/entities.spec.js
@@ -0,0 +1,60 @@
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+import { decode } from './entities.js';
+
+/*
+Test cases shamelessly stolen from https://github.com/fb55/entities/blob/master/src/decode.spec.ts
+Reproduced under BSD 2 license
+---
+Copyright (c) Felix Böhm
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+const tests = [
+	{ input: '&amp;amp;', output: '&amp;' },
+	{ input: '&amp;#38;', output: '&#38;' },
+	{ input: '&amp;#x26;', output: '&#x26;' },
+	{ input: '&amp;#X26;', output: '&#X26;' },
+	{ input: '&#38;#38;', output: '&#38;' },
+	{ input: '&#x26;#38;', output: '&#38;' },
+	{ input: '&#X26;#38;', output: '&#38;' },
+	{ input: '&#x3a;', output: ':' },
+	{ input: '&#x3A;', output: ':' },
+	{ input: '&#X3a;', output: ':' },
+	{ input: '&#X3A;', output: ':' },
+	{ input: '&>', output: '&>' },
+	{ input: 'id=770&#anchor', output: 'id=770&#anchor' }
+];
+
+for (const { input, output } of tests) {
+	test(input, () => {
+		assert.is(decode(input), output);
+	});
+}
+
+test('should HTML decode partial legacy entity', () => {
+	assert.is(decode('&timesbar'), '×bar');
+});
+
+test('should HTML decode legacy entities according to spec', () => {
+	assert.is(decode('?&image_uri=1&ℑ=2&image=3'), '?&image_uri=1&ℑ=2&image=3');
+});
+
+test('should back out of legacy entities', () => {
+	assert.is(decode('&ampa'), '&a');
+});
+
+test('should parse &nbsp followed by < (#852)', () => {
+	assert.is(decode('&nbsp<'), '\u00a0<');
+});
+
+test.run();
diff --git a/packages/kit/src/core/prerender/fixtures/href-with-character-reference/input.html b/packages/kit/src/core/prerender/fixtures/href-with-character-reference/input.html
new file mode 100644
index 000000000..d0f38a450
--- /dev/null
+++ b/packages/kit/src/core/prerender/fixtures/href-with-character-reference/input.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<link rel="stylesheet" href="/styles.css" />
+		<link rel="icon" href="/favicon.png" />
+	</head>
+	<body>
+		<a href="/test&amp;ampersand">/test&amp;ampersand</a>
+		<a href="/test&quot;quotation">/test&quot;quotation</a>
+		<a href="/test&#9825;decimal">/test&#9825;decimal</a>
+		<a href="/test&#x2665;hex">/test&#x2665;hex</a>
+	</body>
+</html>
diff --git a/packages/kit/src/core/prerender/fixtures/href-with-character-reference/output.json b/packages/kit/src/core/prerender/fixtures/href-with-character-reference/output.json
new file mode 100644
index 000000000..904636f65
--- /dev/null
+++ b/packages/kit/src/core/prerender/fixtures/href-with-character-reference/output.json
@@ -0,0 +1,1 @@
+["/styles.css", "/favicon.png", "/test&ampersand", "/test\"quotation", "/test♡decimal", "/test♥hex"]
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index c386bf1ed..852a02a86 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -164,13 +164,16 @@ function create_routes_and_nodes(cwd, config, fallback) {
 
 			const dir = path.join(cwd, routes_base, id);
 
-			const files = fs.readdirSync(dir, {
-				withFileTypes: true
-			});
+			// We can't use withFileTypes because of a NodeJs bug which returns wrong results
+			// with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
+			const files = fs.readdirSync(dir).map((name) => ({
+				is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
+				name
+			}));
 
 			// process files first
 			for (const file of files) {
-				if (file.isDirectory()) continue;
+				if (file.is_dir) continue;
 				if (!file.name.startsWith('+')) continue;
 				if (!valid_extensions.find((ext) => file.name.endsWith(ext))) continue;
 
@@ -213,7 +216,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
 
 			// then handle children
 			for (const file of files) {
-				if (file.isDirectory()) {
+				if (file.is_dir) {
 					walk(depth + 1, path.posix.join(id, file.name), file.name, route);
 				}
 			}
@@ -222,102 +225,115 @@ function create_routes_and_nodes(cwd, config, fallback) {
 		walk(0, '', '', null);
 
 		const root = /** @type {import('types').RouteData} */ (route_map.get(''));
-
-		// TODO remove for 1.0
 		if (route_map.size === 1) {
 			if (!root.leaf && !root.error && !root.layout && !root.endpoint) {
 				throw new Error(
+					// TODO adjust this error message for 1.0
+					// 'No routes found. If you are using a custom src/routes directory, make sure it is specified in svelte.config.js'
 					'The filesystem router API has changed, see https://github.com/sveltejs/kit/discussions/5774 for details'
 				);
 			}
 		}
+	} else {
+		// If there's no routes directory, we'll just create a single empty route. This ensures the root layout and
+		// error components are included in the manifest, which is needed for subsequent build/dev commands to work
+		route_map.set('', {
+			id: '',
+			segment: '',
+			pattern: /^$/,
+			names: [],
+			types: [],
+			parent: null,
+			layout: null,
+			error: null,
+			leaf: null,
+			page: null,
+			endpoint: null
+		});
+	}
 
-		if (!root.layout?.component) {
-			if (!root.layout) root.layout = { depth: 0, child_pages: [] };
-			root.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`));
-		}
-
-		if (!root.error?.component) {
-			if (!root.error) root.error = { depth: 0 };
-			root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`));
-		}
+	const root = /** @type {import('types').RouteData} */ (route_map.get(''));
 
-		// we do layouts/errors first as they are more likely to be reused,
-		// and smaller indexes take fewer bytes. also, this guarantees that
-		// the default error/layout are 0/1
-		route_map.forEach((route) => {
-			if (route.layout) nodes.push(route.layout);
-			if (route.error) nodes.push(route.error);
-		});
+	if (!root.layout?.component) {
+		if (!root.layout) root.layout = { depth: 0, child_pages: [] };
+		root.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`));
+	}
 
-		/** @type {Map<string, string>} */
-		const conflicts = new Map();
+	if (!root.error?.component) {
+		if (!root.error) root.error = { depth: 0 };
+		root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`));
+	}
 
-		route_map.forEach((route) => {
-			if (!route.leaf) return;
+	// we do layouts/errors first as they are more likely to be reused,
+	// and smaller indexes take fewer bytes. also, this guarantees that
+	// the default error/layout are 0/1
+	route_map.forEach((route) => {
+		if (route.layout) nodes.push(route.layout);
+		if (route.error) nodes.push(route.error);
+	});
 
-			nodes.push(route.leaf);
+	/** @type {Map<string, string>} */
+	const conflicts = new Map();
 
-			const normalized = route.id.split('/').filter(affects_path).join('/');
+	route_map.forEach((route) => {
+		if (!route.leaf) return;
 
-			if (conflicts.has(normalized)) {
-				throw new Error(`${conflicts.get(normalized)} and ${route.id} occupy the same route`);
-			}
+		nodes.push(route.leaf);
 
-			conflicts.set(normalized, route.id);
-		});
+		const normalized = route.id.split('/').filter(affects_path).join('/');
 
-		const indexes = new Map(nodes.map((node, i) => [node, i]));
+		if (conflicts.has(normalized)) {
+			throw new Error(`${conflicts.get(normalized)} and ${route.id} occupy the same route`);
+		}
 
-		route_map.forEach((route) => {
-			if (!route.leaf) return;
+		conflicts.set(normalized, route.id);
+	});
 
-			if (route.leaf && route.endpoint) {
-				// TODO possibly relax this https://github.com/sveltejs/kit/issues/5896
-				throw new Error(`${route.endpoint.file} cannot share a directory with other route files`);
-			}
+	const indexes = new Map(nodes.map((node, i) => [node, i]));
 
-			route.page = {
-				layouts: [],
-				errors: [],
-				leaf: /** @type {number} */ (indexes.get(route.leaf))
-			};
+	route_map.forEach((route) => {
+		if (!route.leaf) return;
 
-			/** @type {import('types').RouteData | null} */
-			let current_route = route;
-			let current_node = route.leaf;
-			let parent_id = route.leaf.parent_id;
-
-			while (current_route) {
-				if (parent_id === undefined || current_route.segment === parent_id) {
-					if (current_route.layout || current_route.error) {
-						route.page.layouts.unshift(
-							current_route.layout ? indexes.get(current_route.layout) : undefined
-						);
-						route.page.errors.unshift(
-							current_route.error ? indexes.get(current_route.error) : undefined
-						);
-					}
+		route.page = {
+			layouts: [],
+			errors: [],
+			leaf: /** @type {number} */ (indexes.get(route.leaf))
+		};
 
-					if (current_route.layout) {
-						/** @type {import('types').PageNode[]} */ (current_route.layout.child_pages).push(
-							route.leaf
-						);
-						current_node.parent = current_node = current_route.layout;
-						parent_id = current_node.parent_id;
-					} else {
-						parent_id = undefined;
-					}
+		/** @type {import('types').RouteData | null} */
+		let current_route = route;
+		let current_node = route.leaf;
+		let parent_id = route.leaf.parent_id;
+
+		while (current_route) {
+			if (parent_id === undefined || current_route.segment === parent_id) {
+				if (current_route.layout || current_route.error) {
+					route.page.layouts.unshift(
+						current_route.layout ? indexes.get(current_route.layout) : undefined
+					);
+					route.page.errors.unshift(
+						current_route.error ? indexes.get(current_route.error) : undefined
+					);
 				}
 
-				current_route = current_route.parent;
+				if (current_route.layout) {
+					/** @type {import('types').PageNode[]} */ (current_route.layout.child_pages).push(
+						route.leaf
+					);
+					current_node.parent = current_node = current_route.layout;
+					parent_id = current_node.parent_id;
+				} else {
+					parent_id = undefined;
+				}
 			}
 
-			if (parent_id !== undefined) {
-				throw new Error(`${current_node.component} references missing segment "${parent_id}"`);
-			}
-		});
-	}
+			current_route = current_route.parent;
+		}
+
+		if (parent_id !== undefined) {
+			throw new Error(`${current_node.component} references missing segment "${parent_id}"`);
+		}
+	});
 
 	const routes = Array.from(route_map.values()).sort((a, b) => compare(a, b, segment_map));
 
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
index f1a863077..031071a4d 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
@@ -173,8 +173,16 @@ test('creates routes with layout', () => {
 
 test('succeeds when routes does not exist', () => {
 	const { nodes, routes } = create('samples/basic/routes');
-	assert.equal(nodes, []);
-	assert.equal(routes, []);
+	assert.equal(nodes.map(simplify_node), [
+		{ component: 'layout.svelte' },
+		{ component: 'error.svelte' }
+	]);
+	assert.equal(routes.map(simplify_route), [
+		{
+			id: '',
+			pattern: '/^$/'
+		}
+	]);
 });
 
 // TODO some characters will need to be URL-encoded in the filename
diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js
index 5204a8de5..a7c82f905 100644
--- a/packages/kit/src/core/sync/write_types/index.js
+++ b/packages/kit/src/core/sync/write_types/index.js
@@ -68,7 +68,7 @@ export async function write_types(config, manifest_data, file) {
 		return;
 	}
 
-	const id = path.posix.relative(config.kit.files.routes, path.dirname(file));
+	const id = posixify(path.relative(config.kit.files.routes, path.dirname(file)));
 
 	const route = manifest_data.routes.find((route) => route.id === id);
 	if (!route) return; // this shouldn't ever happen
diff --git a/packages/kit/src/exports/node/index.js b/packages/kit/src/exports/node/index.js
index b73c5c91b..67b82e63f 100644
--- a/packages/kit/src/exports/node/index.js
+++ b/packages/kit/src/exports/node/index.js
@@ -1,4 +1,5 @@
 import * as set_cookie_parser from 'set-cookie-parser';
+import { error } from '../index.js';
 
 /**
  * @param {import('http').IncomingMessage} req
@@ -27,7 +28,8 @@ function get_raw_body(req, body_size_limit) {
 		if (!length) {
 			length = body_size_limit;
 		} else if (length > body_size_limit) {
-			throw new Error(
+			throw error(
+				413,
 				`Received content-length of ${length}, but only accept up to ${body_size_limit} bytes.`
 			);
 		}
@@ -45,6 +47,7 @@ function get_raw_body(req, body_size_limit) {
 	return new ReadableStream({
 		start(controller) {
 			req.on('error', (error) => {
+				cancelled = true;
 				controller.error(error);
 			});
 
@@ -58,8 +61,10 @@ function get_raw_body(req, body_size_limit) {
 
 				size += chunk.length;
 				if (size > length) {
-					req.destroy(
-						new Error(
+					cancelled = true;
+					controller.error(
+						error(
+							413,
 							`request body size exceeded ${
 								content_length ? "'content-length'" : 'BODY_SIZE_LIMIT'
 							} of ${length}`
diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js
index 77011b11e..0f1120e5f 100644
--- a/packages/kit/src/exports/vite/build/build_server.js
+++ b/packages/kit/src/exports/vite/build/build_server.js
@@ -4,7 +4,13 @@ import { mkdirp, posixify, resolve_entry } from '../../../utils/filesystem.js';
 import { get_vite_config, merge_vite_configs } from '../utils.js';
 import { load_error_page, load_template } from '../../../core/config/index.js';
 import { runtime_directory } from '../../../core/utils.js';
-import { create_build, find_deps, get_default_build_config, is_http_method } from './utils.js';
+import {
+	create_build,
+	find_deps,
+	get_default_build_config,
+	is_http_method,
+	resolve_symlinks
+} from './utils.js';
 import { s } from '../../../utils/misc.js';
 
 /**
@@ -68,7 +74,7 @@ export class Server {
 					get request() {
 						throw new Error('request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details');
 					}
-				}) ?? { message: 'Internal Error' };
+				}) ?? { message: event.routeId ? 'Internal Error' : 'Not Found' };
 			},
 			hooks: null,
 			manifest,
@@ -76,7 +82,7 @@ export class Server {
 			public_env: {},
 			read,
 			root,
-			service_worker: ${has_service_worker ? "base + '/service-worker.js'" : 'null'},
+			service_worker: ${has_service_worker},
 			app_template,
 			app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
 			error_template,
@@ -285,7 +291,7 @@ export async function build_server(options, client) {
 
 			exports.push(
 				`export const component = async () => (await import('../${
-					vite_manifest[node.component].file
+					resolve_symlinks(vite_manifest, node.component).chunk.file
 				}')).default;`,
 				`export const file = '${entry.file}';` // TODO what is this?
 			);
diff --git a/packages/kit/src/exports/vite/build/utils.js b/packages/kit/src/exports/vite/build/utils.js
index 4a0e19e4a..da3134c31 100644
--- a/packages/kit/src/exports/vite/build/utils.js
+++ b/packages/kit/src/exports/vite/build/utils.js
@@ -1,3 +1,5 @@
+import fs from 'fs';
+import path from 'path';
 import * as vite from 'vite';
 import { get_aliases } from '../utils.js';
 
@@ -44,14 +46,14 @@ export function find_deps(manifest, entry, add_dynamic_css) {
 	const stylesheets = new Set();
 
 	/**
-	 * @param {string} file
+	 * @param {string} current
 	 * @param {boolean} add_js
 	 */
-	function traverse(file, add_js) {
-		if (seen.has(file)) return;
-		seen.add(file);
+	function traverse(current, add_js) {
+		if (seen.has(current)) return;
+		seen.add(current);
 
-		const chunk = manifest[file];
+		const { chunk } = resolve_symlinks(manifest, current);
 
 		if (add_js) imports.add(chunk.file);
 
@@ -68,15 +70,31 @@ export function find_deps(manifest, entry, add_dynamic_css) {
 		}
 	}
 
-	traverse(entry, true);
+	const { chunk, file } = resolve_symlinks(manifest, entry);
+
+	traverse(file, true);
 
 	return {
-		file: manifest[entry].file,
+		file: chunk.file,
 		imports: Array.from(imports),
 		stylesheets: Array.from(stylesheets)
 	};
 }
 
+/**
+ * @param {import('vite').Manifest} manifest
+ * @param {string} file
+ */
+export function resolve_symlinks(manifest, file) {
+	while (!manifest[file]) {
+		file = path.relative('.', fs.realpathSync(file));
+	}
+
+	const chunk = manifest[file];
+
+	return { chunk, file };
+}
+
 /**
  * The Vite configuration that we use by default.
  * @param {{
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index 3c28acb48..3c971ce19 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -350,7 +350,7 @@ export async function dev(vite, vite_config, svelte_config) {
 						user_hooks.handleError ||
 						(({ error: e }) => {
 							const error = /** @type {Error & { frame?: string }} */ (e);
-							console.error(colors.bold().red(error.message));
+							console.error(colors.bold().red(error.message ?? error)); // Could be anything
 							if (error.frame) {
 								console.error(colors.gray(error.frame));
 							}
@@ -397,7 +397,7 @@ export async function dev(vite, vite_config, svelte_config) {
 					});
 				} catch (/** @type {any} */ err) {
 					res.statusCode = err.status || 400;
-					return res.end(err.message || 'Invalid request body');
+					return res.end('Invalid request body');
 				}
 
 				const template = load_template(cwd, svelte_config);
@@ -432,7 +432,7 @@ export async function dev(vite, vite_config, svelte_config) {
 											'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
 										);
 									}
-								}) ?? { message: 'Internal Error' }
+								}) ?? { message: event.routeId ? 'Internal Error' : 'Not Found' }
 							);
 						},
 						hooks,
@@ -460,6 +460,7 @@ export async function dev(vite, vite_config, svelte_config) {
 								.replace(/%sveltekit\.status%/g, String(status))
 								.replace(/%sveltekit\.error\.message%/g, message);
 						},
+						service_worker: false,
 						trailing_slash: svelte_config.kit.trailingSlash
 					},
 					{
diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js
index 55a59105b..0a24a0b9c 100644
--- a/packages/kit/src/exports/vite/preview/index.js
+++ b/packages/kit/src/exports/vite/preview/index.js
@@ -137,7 +137,7 @@ export async function preview(vite, vite_config, svelte_config) {
 				});
 			} catch (/** @type {any} */ err) {
 				res.statusCode = err.status || 400;
-				return res.end(err.message || 'Invalid request body');
+				return res.end('Invalid request body');
 			}
 
 			setResponse(
diff --git a/packages/kit/src/runtime/app/forms.js b/packages/kit/src/runtime/app/forms.js
index 6ab589caa..bf0601385 100644
--- a/packages/kit/src/runtime/app/forms.js
+++ b/packages/kit/src/runtime/app/forms.js
@@ -28,7 +28,13 @@ export function enhance(form, submit = () => {}) {
 			await invalidateAll();
 		}
 
-		if (location.origin + location.pathname === action.origin + action.pathname) {
+		// For success/invalid results, only apply action if it belongs to the
+		// current page, otherwise `form` will be updated erroneously
+		if (
+			location.origin + location.pathname === action.origin + action.pathname ||
+			result.type === 'redirect' ||
+			result.type === 'error'
+		) {
 			applyAction(result);
 		}
 	};
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 106b0c918..23046c111 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -73,11 +73,8 @@ export function create_client({ target, base, trailing_slash }) {
 	/** @type {Array<((url: URL) => boolean)>} */
 	const invalidated = [];
 
-	/** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null}} */
-	const load_cache = {
-		id: null,
-		promise: null
-	};
+	/** @type {{id: string, promise: Promise<import('./types').NavigationResult | undefined>} | null} */
+	let load_cache = null;
 
 	const callbacks = {
 		/** @type {Array<(navigation: import('types').Navigation & { cancel: () => void }) => void>} */
@@ -91,7 +88,6 @@ export function create_client({ target, base, trailing_slash }) {
 	let current = {
 		branch: [],
 		error: null,
-		session_id: 0,
 		// @ts-ignore - we need the initial value to be null
 		url: null
 	};
@@ -99,10 +95,6 @@ export function create_client({ target, base, trailing_slash }) {
 	let started = false;
 	let autoscroll = true;
 	let updating = false;
-	let session_id = 1;
-
-	/** @type {Promise<void> | null} */
-	let invalidating = null;
 	let force_invalidation = false;
 
 	/** @type {import('svelte').SvelteComponent} */
@@ -140,31 +132,38 @@ export function create_client({ target, base, trailing_slash }) {
 	/** @type {{}} */
 	let token;
 
-	function invalidate() {
-		if (!invalidating) {
-			const url = new URL(location.href);
-
-			invalidating = Promise.resolve().then(async () => {
-				const intent = get_navigation_intent(url, true);
-				await update(intent, url, []);
-
-				invalidating = null;
-				force_invalidation = false;
-			});
-		}
-
-		return invalidating;
+	/** @type {Promise<void> | null} */
+	let pending_invalidate;
+
+	async function invalidate() {
+		// Accept all invalidations as they come, don't swallow any while another invalidation
+		// is running because subsequent invalidations may make earlier ones outdated,
+		// but batch multiple synchronous invalidations.
+		pending_invalidate = pending_invalidate || Promise.resolve();
+		await pending_invalidate;
+		pending_invalidate = null;
+
+		const url = new URL(location.href);
+		const intent = get_navigation_intent(url, true);
+		// Clear prefetch, it might be affected by the invalidation.
+		// Also solves an edge case where a prefetch is triggered, the navigation for it
+		// was then triggered and is still running while the invalidation kicks in,
+		// at which point the invalidation should take over and "win".
+		load_cache = null;
+		await update(intent, url, []);
 	}
 
 	/**
 	 * @param {string | URL} url
 	 * @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any }} opts
 	 * @param {string[]} redirect_chain
+	 * @param {{}} [nav_token]
 	 */
 	async function goto(
 		url,
 		{ noscroll = false, replaceState = false, keepfocus = false, state = {} },
-		redirect_chain
+		redirect_chain,
+		nav_token
 	) {
 		if (typeof url === 'string') {
 			url = new URL(url, get_base_uri(document));
@@ -179,6 +178,7 @@ export function create_client({ target, base, trailing_slash }) {
 				state,
 				replaceState
 			},
+			nav_token,
 			accepted: () => {},
 			blocked: () => {},
 			type: 'goto'
@@ -193,8 +193,7 @@ export function create_client({ target, base, trailing_slash }) {
 			throw new Error('Attempted to prefetch a URL that does not belong to this app');
 		}
 
-		load_cache.promise = load_route(intent);
-		load_cache.id = intent.id;
+		load_cache = { id: intent.id, promise: load_route(intent) };
 
 		return load_cache.promise;
 	}
@@ -205,10 +204,11 @@ export function create_client({ target, base, trailing_slash }) {
 	 * @param {URL} url
 	 * @param {string[]} redirect_chain
 	 * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
+	 * @param {{}} [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token
 	 * @param {() => void} [callback]
 	 */
-	async function update(intent, url, redirect_chain, opts, callback) {
-		const current_token = (token = {});
+	async function update(intent, url, redirect_chain, opts, nav_token = {}, callback) {
+		token = nav_token;
 		let navigation_result = intent && (await load_route(intent));
 
 		if (
@@ -239,9 +239,7 @@ export function create_client({ target, base, trailing_slash }) {
 		url = intent?.url || url;
 
 		// abort if user navigated during update
-		if (token !== current_token) return false;
-
-		invalidated.length = 0;
+		if (token !== nav_token) return false;
 
 		if (navigation_result.type === 'redirect') {
 			if (redirect_chain.length > 10 || redirect_chain.includes(url.pathname)) {
@@ -252,7 +250,12 @@ export function create_client({ target, base, trailing_slash }) {
 					routeId: null
 				});
 			} else {
-				goto(new URL(navigation_result.location, url).href, {}, [...redirect_chain, url.pathname]);
+				goto(
+					new URL(navigation_result.location, url).href,
+					{},
+					[...redirect_chain, url.pathname],
+					nav_token
+				);
 				return false;
 			}
 		} else if (navigation_result.props?.page?.status >= 400) {
@@ -262,6 +265,11 @@ export function create_client({ target, base, trailing_slash }) {
 			}
 		}
 
+		// reset invalidation only after a finished navigation. If there are redirects or
+		// additional invalidations, they should get the same invalidation treatment
+		invalidated.length = 0;
+		force_invalidation = false;
+
 		updating = true;
 
 		if (opts && opts.details) {
@@ -271,6 +279,9 @@ export function create_client({ target, base, trailing_slash }) {
 			history[details.replaceState ? 'replaceState' : 'pushState'](details.state, '', url);
 		}
 
+		// reset prefetch synchronously after the history state has been set to avoid race conditions
+		load_cache = null;
+
 		if (started) {
 			current = navigation_result.state;
 
@@ -334,8 +345,6 @@ export function create_client({ target, base, trailing_slash }) {
 			await tick();
 		}
 
-		load_cache.promise = null;
-		load_cache.id = null;
 		autoscroll = true;
 
 		if (navigation_result.props.page) {
@@ -410,8 +419,7 @@ export function create_client({ target, base, trailing_slash }) {
 				params,
 				branch,
 				error,
-				route,
-				session_id
+				route
 			},
 			props: {
 				components: filtered.map((branch_node) => branch_node.node.component)
@@ -513,22 +521,15 @@ export function create_client({ target, base, trailing_slash }) {
 				}
 			}
 
-			/** @type {Record<string, string>} */
-			const uses_params = {};
-			for (const key in params) {
-				Object.defineProperty(uses_params, key, {
-					get() {
-						uses.params.add(key);
-						return params[key];
-					},
-					enumerable: true
-				});
-			}
-
 			/** @type {import('types').LoadEvent} */
 			const load_input = {
 				routeId,
-				params: uses_params,
+				params: new Proxy(params, {
+					get: (target, key) => {
+						uses.params.add(/** @type {string} */ (key));
+						return target[/** @type {string} */ (key)];
+					}
+				}),
 				data: server_data_node?.data ?? null,
 				url: make_trackable(url, () => {
 					uses.url = true;
@@ -536,9 +537,7 @@ export function create_client({ target, base, trailing_slash }) {
 				async fetch(resource, init) {
 					let requested;
 
-					if (typeof resource === 'string') {
-						requested = resource;
-					} else {
+					if (resource instanceof Request) {
 						requested = resource.url;
 
 						// we're not allowed to modify the received `Request` object, so in order
@@ -563,6 +562,8 @@ export function create_client({ target, base, trailing_slash }) {
 							signal: resource.signal,
 							...init
 						};
+					} else {
+						requested = resource;
 					}
 
 					// we must fixup relative urls so they are resolved from the target page
@@ -632,20 +633,21 @@ export function create_client({ target, base, trailing_slash }) {
 	}
 
 	/**
-	 * @param {import('types').Uses | undefined} uses
+	 * @param {boolean} url_changed
 	 * @param {boolean} parent_changed
-	 * @param {{ url: boolean, params: string[] }} changed
+	 * @param {import('types').Uses | undefined} uses
+	 * @param {Record<string, string>} params
 	 */
-	function has_changed(changed, parent_changed, uses) {
+	function has_changed(url_changed, parent_changed, uses, params) {
 		if (force_invalidation) return true;
 
 		if (!uses) return false;
 
 		if (uses.parent && parent_changed) return true;
-		if (changed.url && uses.url) return true;
+		if (uses.url && url_changed) return true;
 
-		for (const param of changed.params) {
-			if (uses.params.has(param)) return true;
+		for (const param of uses.params) {
+			if (params[param] !== current.params[param]) return true;
 		}
 
 		for (const href of uses.dependencies) {
@@ -683,17 +685,12 @@ export function create_client({ target, base, trailing_slash }) {
 	 * @returns {Promise<import('./types').NavigationResult | undefined>}
 	 */
 	async function load_route({ id, invalidating, url, params, route }) {
-		if (load_cache.id === id && load_cache.promise) {
+		if (load_cache?.id === id) {
 			return load_cache.promise;
 		}
 
 		const { errors, layouts, leaf } = route;
 
-		const changed = current.url && {
-			url: id !== current.url.pathname + current.url.search,
-			params: Object.keys(params).filter((key) => current.params[key] !== params[key])
-		};
-
 		const loaders = [...layouts, leaf];
 
 		// preload modules to avoid waterfall, but handle rejections
@@ -705,12 +702,15 @@ export function create_client({ target, base, trailing_slash }) {
 		/** @type {import('types').ServerData | null} */
 		let server_data = null;
 
+		const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
+
 		const invalid_server_nodes = loaders.reduce((acc, loader, i) => {
 			const previous = current.branch[i];
+
 			const invalid =
 				!!loader?.[0] &&
 				(previous?.loader !== loader[1] ||
-					has_changed(changed, acc.some(Boolean), previous.server?.uses));
+					has_changed(url_changed, acc.some(Boolean), previous.server?.uses, params));
 
 			acc.push(invalid);
 			return acc;
@@ -749,7 +749,7 @@ export function create_client({ target, base, trailing_slash }) {
 			const valid =
 				(!server_data_node || server_data_node.type === 'skip') &&
 				loader[1] === previous?.loader &&
-				!has_changed(changed, parent_changed, previous.shared?.uses);
+				!has_changed(url_changed, parent_changed, previous.shared?.uses, params);
 			if (valid) return previous;
 
 			parent_changed = true;
@@ -814,40 +814,22 @@ export function create_client({ target, base, trailing_slash }) {
 						error = handle_error(err, { params, url, routeId: route.id });
 					}
 
-					while (i--) {
-						if (errors[i]) {
-							/** @type {import('./types').BranchNode | undefined} */
-							let error_loaded;
-
-							let j = i;
-							while (!branch[j]) j -= 1;
-							try {
-								error_loaded = {
-									node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
-									loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
-									data: {},
-									server: null,
-									shared: null
-								};
-
-								return await get_navigation_result_from_branch({
-									url,
-									params,
-									branch: branch.slice(0, j + 1).concat(error_loaded),
-									status,
-									error,
-									route
-								});
-							} catch (e) {
-								continue;
-							}
-						}
+					const error_load = await load_nearest_error_page(i, branch, errors);
+					if (error_load) {
+						return await get_navigation_result_from_branch({
+							url,
+							params,
+							branch: branch.slice(0, error_load.idx).concat(error_load.node),
+							status,
+							error,
+							route
+						});
+					} else {
+						// if we get here, it's because the root `load` function failed,
+						// and we need to fall back to the server
+						await native_navigation(url);
+						return;
 					}
-
-					// if we get here, it's because the root `load` function failed,
-					// and we need to fall back to the server
-					await native_navigation(url);
-					return;
 				}
 			} else {
 				// push an empty slot so we can rewind past gaps to the
@@ -868,6 +850,35 @@ export function create_client({ target, base, trailing_slash }) {
 		});
 	}
 
+	/**
+	 * @param {number} i Start index to backtrack from
+	 * @param {Array<import('./types').BranchNode | undefined>} branch Branch to backtrack
+	 * @param {Array<import('types').CSRPageNodeLoader | undefined>} errors All error pages for this branch
+	 * @returns {Promise<{idx: number; node: import('./types').BranchNode} | undefined>}
+	 */
+	async function load_nearest_error_page(i, branch, errors) {
+		while (i--) {
+			if (errors[i]) {
+				let j = i;
+				while (!branch[j]) j -= 1;
+				try {
+					return {
+						idx: j + 1,
+						node: {
+							node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
+							loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
+							data: {},
+							server: null,
+							shared: null
+						}
+					};
+				} catch (e) {
+					continue;
+				}
+			}
+		}
+	}
+
 	/**
 	 * @param {{
 	 *   status: number;
@@ -981,6 +992,7 @@ export function create_client({ target, base, trailing_slash }) {
 	 *   } | null;
 	 *   type: import('types').NavigationType;
 	 *   delta?: number;
+	 *   nav_token?: {};
 	 *   accepted: () => void;
 	 *   blocked: () => void;
 	 * }} opts
@@ -993,6 +1005,7 @@ export function create_client({ target, base, trailing_slash }) {
 		details,
 		type,
 		delta,
+		nav_token,
 		accepted,
 		blocked
 	}) {
@@ -1050,6 +1063,7 @@ export function create_client({ target, base, trailing_slash }) {
 				keepfocus,
 				details
 			},
+			nav_token,
 			() => {
 				callbacks.after_navigate.forEach((fn) => fn(navigation));
 				stores.navigating.set(null);
@@ -1157,44 +1171,26 @@ export function create_client({ target, base, trailing_slash }) {
 				const { branch, route } = current;
 				if (!route) return;
 
-				let i = current.branch.length;
-
-				while (i--) {
-					if (route.errors[i]) {
-						/** @type {import('./types').BranchNode | undefined} */
-						let error_loaded;
-
-						let j = i;
-						while (!branch[j]) j -= 1;
-						try {
-							error_loaded = {
-								node: await /** @type {import('types').CSRPageNodeLoader } */ (route.errors[i])(),
-								loader: /** @type {import('types').CSRPageNodeLoader } */ (route.errors[i]),
-								data: {},
-								server: null,
-								shared: null
-							};
-
-							const navigation_result = await get_navigation_result_from_branch({
-								url,
-								params: current.params,
-								branch: branch.slice(0, j + 1).concat(error_loaded),
-								status: 500, // TODO might not be 500?
-								error: result.error,
-								route
-							});
-
-							current = navigation_result.state;
-
-							const post_update = pre_update();
-							root.$set(navigation_result.props);
-							post_update();
-
-							return;
-						} catch (e) {
-							continue;
-						}
-					}
+				const error_load = await load_nearest_error_page(
+					current.branch.length,
+					branch,
+					route.errors
+				);
+				if (error_load) {
+					const navigation_result = await get_navigation_result_from_branch({
+						url,
+						params: current.params,
+						branch: branch.slice(0, error_load.idx).concat(error_load.node),
+						status: 500, // TODO might not be 500?
+						error: result.error,
+						route
+					});
+
+					current = navigation_result.state;
+
+					const post_update = pre_update();
+					root.$set(navigation_result.props);
+					post_update();
 				}
 			} else if (result.type === 'redirect') {
 				goto(result.location, {}, []);
@@ -1203,10 +1199,8 @@ export function create_client({ target, base, trailing_slash }) {
 				const props = { form: result.data };
 
 				if (result.status !== page.status) {
-					props.page = {
-						...page,
-						status: result.status
-					};
+					page = { ...page, status: result.status };
+					props.page = page;
 				}
 
 				const post_update = pre_update();
@@ -1513,7 +1507,10 @@ async function load_data(url, invalid) {
  * @returns {App.PageError}
  */
 function handle_error(error, event) {
-	return hooks.handleError({ error, event }) ?? /** @type {any} */ ({ message: 'Internal Error' });
+	return (
+		hooks.handleError({ error, event }) ??
+		/** @type {any} */ ({ message: event.routeId ? 'Internal Error' : 'Not Found' })
+	);
 }
 
 // TODO remove for 1.0
@@ -1543,7 +1540,8 @@ function add_url_properties(type, target) {
 				throw new Error(
 					`The navigation shape changed - ${type}.${prop} should now be ${type}.url.${prop}`
 				);
-			}
+			},
+			enumerable: false
 		});
 	}
 
diff --git a/packages/kit/src/runtime/client/fetcher.js b/packages/kit/src/runtime/client/fetcher.js
index 1552656fa..e355ee2db 100644
--- a/packages/kit/src/runtime/client/fetcher.js
+++ b/packages/kit/src/runtime/client/fetcher.js
@@ -62,12 +62,12 @@ const cache = new Map();
 /**
  * Should be called on the initial run of load functions that hydrate the page.
  * Saves any requests with cache-control max-age to the cache.
- * @param {RequestInfo} resource
+ * @param {RequestInfo | URL} resource
  * @param {string} resolved
  * @param {RequestInit} [opts]
  */
 export function initial_fetch(resource, resolved, opts) {
-	const url = JSON.stringify(typeof resource === 'string' ? resource : resource.url);
+	const url = JSON.stringify(resource instanceof Request ? resource.url : resource);
 
 	let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
 
diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts
index a566a4ba2..4e88d9132 100644
--- a/packages/kit/src/runtime/client/types.d.ts
+++ b/packages/kit/src/runtime/client/types.d.ts
@@ -80,6 +80,5 @@ export interface NavigationState {
 	error: App.PageError | null;
 	params: Record<string, string>;
 	route: CSRRoute | null;
-	session_id: number;
 	url: URL;
 }
diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js
index 559dcce3e..69177f213 100644
--- a/packages/kit/src/runtime/server/cookie.js
+++ b/packages/kit/src/runtime/server/cookie.js
@@ -71,6 +71,18 @@ export function get_cookies(request, url) {
 					maxAge: 0
 				}
 			});
+		},
+
+		/**
+		 * @param {string} name
+		 * @param {string} value
+		 * @param {import('cookie').CookieSerializeOptions} opts
+		 */
+		serialize(name, value, opts) {
+			return serialize(name, value, {
+				...DEFAULT_SERIALIZE_OPTIONS,
+				...opts
+			});
 		}
 	};
 
diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js
index 5e3aba084..f80ed4417 100644
--- a/packages/kit/src/runtime/server/endpoint.js
+++ b/packages/kit/src/runtime/server/endpoint.js
@@ -1,4 +1,5 @@
 import { json } from '../../exports/index.js';
+import { negotiate } from '../../utils/http.js';
 import { Redirect, ValidationError } from '../control.js';
 import { check_method_names, method_not_allowed } from './utils.js';
 
@@ -64,3 +65,19 @@ export async function render_endpoint(event, mod, state) {
 		throw error;
 	}
 }
+
+/**
+ * @param {import('types').RequestEvent} event
+ */
+export function is_endpoint_request(event) {
+	const { method } = event.request;
+
+	if (method === 'PUT' || method === 'PATCH' || method === 'DELETE') {
+		// These methods exist exclusively for endpoints
+		return true;
+	}
+
+	// GET/POST requests may be for endpoints or pages. We prefer endpoints if this isn't a text/html request
+	const accept = event.request.headers.get('accept') ?? '*/*';
+	return negotiate(accept, ['*', 'text/html']) !== 'text/html';
+}
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index cff90f3c0..7b1daadde 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -1,4 +1,4 @@
-import { render_endpoint } from './endpoint.js';
+import { is_endpoint_request, render_endpoint } from './endpoint.js';
 import { render_page } from './page/index.js';
 import { render_response } from './page/render.js';
 import { respond_with_error } from './page/respond_with_error.js';
@@ -226,27 +226,16 @@ export async function respond(request, options, state) {
 
 				if (is_data_request) {
 					response = await render_data(event, route, options, state);
+				} else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
+					response = await render_endpoint(event, await route.endpoint(), state);
 				} else if (route.page) {
 					response = await render_page(event, route, route.page, options, state, resolve_opts);
-				} else if (route.endpoint) {
-					response = await render_endpoint(event, await route.endpoint(), state);
 				} else {
 					// a route will always have a page or an endpoint, but TypeScript
 					// doesn't know that
 					throw new Error('This should never happen');
 				}
 
-				if (!is_data_request) {
-					// we only want to set cookies on __data.js requests, we don't
-					// want to cache stuff erroneously etc
-					for (const key in headers) {
-						const value = headers[key];
-						response.headers.set(key, /** @type {string} */ (value));
-					}
-				}
-
-				add_cookies_to_headers(response.headers, Array.from(new_cookies.values()));
-
 				return response;
 			}
 
@@ -294,7 +283,21 @@ export async function respond(request, options, state) {
 	try {
 		const response = await options.hooks.handle({
 			event,
-			resolve,
+			resolve: (event, opts) =>
+				resolve(event, 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`
+					if (!is_data_request) {
+						// we only want to set cookies on __data.js requests, we don't
+						// want to cache stuff erroneously etc
+						for (const key in headers) {
+							const value = headers[key];
+							response.headers.set(key, /** @type {string} */ (value));
+						}
+					}
+					add_cookies_to_headers(response.headers, Array.from(new_cookies.values()));
+					return response;
+				}),
 			// TODO remove for 1.0
 			// @ts-expect-error
 			get request() {
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 16a060597..282369140 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -1,5 +1,5 @@
 import { escape_html_attr } from '../../../utils/escape.js';
-import { sha256, base64 } from './crypto.js';
+import { base64, sha256 } from './crypto.js';
 
 const array = new Uint8Array(16);
 
@@ -15,7 +15,8 @@ const quoted = new Set([
 	'unsafe-inline',
 	'none',
 	'strict-dynamic',
-	'report-sample'
+	'report-sample',
+	'wasm-unsafe-eval'
 ]);
 
 const crypto_pattern = /^(nonce|sha\d\d\d)-/;
diff --git a/packages/kit/src/runtime/server/page/fetch.js b/packages/kit/src/runtime/server/page/fetch.js
index ec728254a..fdd0a4658 100644
--- a/packages/kit/src/runtime/server/page/fetch.js
+++ b/packages/kit/src/runtime/server/page/fetch.js
@@ -172,21 +172,21 @@ export function create_fetch({ event, options, state, route, prerender_default,
 					state.prerendering.dependencies.set(url.pathname, dependency);
 				}
 
+				const set_cookie = response.headers.get('set-cookie');
+				if (set_cookie) {
+					set_cookies.push(
+						...set_cookie_parser.splitCookiesString(set_cookie).map((str) => {
+							const { name, value, ...options } = set_cookie_parser.parseString(str);
+							// options.sameSite is string, something more specific is required - type cast is safe
+							return /** @type{import('./types').Cookie} */ ({ name, value, options });
+						})
+					);
+				}
+
 				return response;
 			}
 		});
 
-		const set_cookie = response.headers.get('set-cookie');
-		if (set_cookie) {
-			set_cookies.push(
-				...set_cookie_parser.splitCookiesString(set_cookie).map((str) => {
-					const { name, value, ...options } = set_cookie_parser.parseString(str);
-					// options.sameSite is string, something more specific is required - type cast is safe
-					return /** @type{import('./types').Cookie} */ ({ name, value, options });
-				})
-			);
-		}
-
 		const proxy = new Proxy(response, {
 			get(response, key, _receiver) {
 				async function text() {
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 308d02e0c..2bb13b92d 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -187,37 +187,6 @@ export async function render_response({
 		serialized.form = devalue(form_value);
 	}
 
-	// prettier-ignore
-	const init_app = `
-		import { start } from ${s(prefixed(entry.file))};
-
-		start({
-			env: ${s(options.public_env)},
-			hydrate: ${page_config.ssr ? `{
-				status: ${status},
-				error: ${s(error)},
-				node_ids: [${branch.map(({ node }) => node.index).join(', ')}],
-				params: ${devalue(event.params)},
-				routeId: ${s(event.routeId)},
-				data: ${serialized.data},
-				form: ${serialized.form}
-			}` : 'null'},
-			paths: ${s(options.paths)},
-			target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode,
-			trailing_slash: ${s(options.trailing_slash)}
-		});
-	`;
-
-	// we use an anonymous function instead of an arrow function to support
-	// older browsers (https://github.com/sveltejs/kit/pull/5417)
-	const init_service_worker = `
-		if ('serviceWorker' in navigator) {
-			addEventListener('load', function () {
-				navigator.serviceWorker.register('${options.service_worker}');
-			});
-		}
-	`;
-
 	if (inline_styles.size > 0) {
 		const content = Array.from(inline_styles.values()).join('\n');
 
@@ -248,15 +217,36 @@ export async function render_response({
 		}
 
 		attributes.unshift('rel="stylesheet"');
-		head += `\n\t<link href="${path}" ${attributes.join(' ')}>`;
+		head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
 	}
 
 	if (page_config.csr) {
+		// prettier-ignore
+		const init_app = `
+			import { start } from ${s(prefixed(entry.file))};
+
+			start({
+				env: ${s(options.public_env)},
+				hydrate: ${page_config.ssr ? `{
+					status: ${status},
+					error: ${s(error)},
+					node_ids: [${branch.map(({ node }) => node.index).join(', ')}],
+					params: ${devalue(event.params)},
+					routeId: ${s(event.routeId)},
+					data: ${serialized.data},
+					form: ${serialized.form}
+				}` : 'null'},
+				paths: ${s(options.paths)},
+				target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode,
+				trailing_slash: ${s(options.trailing_slash)}
+			});
+		`;
+
 		for (const dep of modulepreloads) {
 			const path = prefixed(dep);
 			link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
 			if (state.prerendering) {
-				head += `\n\t<link rel="modulepreload" href="${path}">`;
+				head += `\n\t\t<link rel="modulepreload" href="${path}">`;
 			}
 		}
 
@@ -280,11 +270,21 @@ export async function render_response({
 	}
 
 	if (options.service_worker) {
+		// we use an anonymous function instead of an arrow function to support
+		// older browsers (https://github.com/sveltejs/kit/pull/5417)
+		const init_service_worker = `
+			if ('serviceWorker' in navigator) {
+				addEventListener('load', function () {
+					navigator.serviceWorker.register('${prefixed('service-worker.js')}');
+				});
+			}
+		`;
+
 		// always include service worker unless it's turned off explicitly
 		csp.add_script(init_service_worker);
 
 		head += `
-			<script${csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''}>${init_service_worker}</script>`;
+		<script${csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''}>${init_service_worker}</script>`;
 	}
 
 	if (state.prerendering) {
diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js
index b0e790f02..76fb26f60 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -4,9 +4,11 @@ import {
 	handle_error_and_jsonify,
 	GENERIC_ERROR,
 	get_option,
-	static_error_page
+	static_error_page,
+	redirect_response
 } from '../utils.js';
 import { create_fetch } from './fetch.js';
+import { HttpError, Redirect } from '../../control.js';
 
 /**
  * @typedef {import('./types.js').Loaded} Loaded
@@ -87,6 +89,16 @@ export async function respond_with_error({ event, options, state, status, error,
 			resolve_opts
 		});
 	} catch (error) {
-		return static_error_page(options, 500, handle_error_and_jsonify(event, options, error).message);
+		// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
+		// we end up here.
+		if (error instanceof Redirect) {
+			return redirect_response(error.status, error.location, cookies);
+		}
+
+		return static_error_page(
+			options,
+			error instanceof HttpError ? error.status : 500,
+			handle_error_and_jsonify(event, options, error).message
+		);
 	}
 }
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index 333491328..c137f9f4f 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -70,21 +70,18 @@ export function allowed_methods(mod) {
 
 /** @param {any} data */
 export function data_response(data) {
+	const headers = {
+		'content-type': 'application/javascript',
+		'cache-control': 'private, no-store'
+	};
+
 	try {
-		return new Response(`window.__sveltekit_data = ${devalue(data)}`, {
-			headers: {
-				'content-type': 'application/javascript'
-			}
-		});
+		return new Response(`window.__sveltekit_data = ${devalue(data)}`, { headers });
 	} catch (e) {
 		const error = /** @type {any} */ (e);
 		const match = /\[(\d+)\]\.data\.(.+)/.exec(error.path);
 		const message = match ? `${error.message} (data.${match[2]})` : error.message;
-		return new Response(`throw new Error(${JSON.stringify(message)})`, {
-			headers: {
-				'content-type': 'application/javascript'
-			}
-		});
+		return new Response(`throw new Error(${JSON.stringify(message)})`, { headers });
 	}
 }
 
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index b26a0a932..c48dba8ad 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -13,7 +13,8 @@ import {
 	PrerenderOnErrorValue,
 	RequestOptions,
 	RouteDefinition,
-	TrailingSlash
+	TrailingSlash,
+	UniqueInterface
 } from './private.js';
 import { SSRNodeLoader, SSRRoute, ValidatedConfig } from './internal.js';
 import { HttpError, Redirect } from '../src/runtime/control.js';
@@ -52,7 +53,8 @@ type OptionalUnion<
 > = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
 
 // Needs to be here, else ActionData will be resolved to unknown - probably because of "d.ts file imports .js file" in combination with allowJs
-interface ValidationError<T extends Record<string, unknown> | undefined = undefined> {
+export interface ValidationError<T extends Record<string, unknown> | undefined = undefined>
+	extends UniqueInterface {
 	status: number;
 	data: T;
 }
@@ -155,6 +157,19 @@ export interface Cookies {
 	 * Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
 	 */
 	delete(name: string, opts?: import('cookie').CookieSerializeOptions): void;
+
+	/**
+	 * Serialize a cookie name-value pair into a Set-Cookie header string.
+	 *
+	 * The `httpOnly` and `secure` options are `true` by default, and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The `sameSite` option defaults to `lax`.
+	 *
+	 * By default, the `path` of a cookie is the current pathname. In most cases you should explicitly set `path: '/'` to make the cookie available throughout your app.
+	 *
+	 * @param name the name for the cookie
+	 * @param value value to set the cookie to
+	 * @param options object containing serialization options
+	 */
+	serialize(name: string, value: string, opts?: import('cookie').CookieSerializeOptions): string;
 }
 
 export interface KitConfig {
@@ -250,7 +265,7 @@ export interface LoadEvent<
 	Data extends Record<string, unknown> | null = Record<string, any> | null,
 	ParentData extends Record<string, unknown> = Record<string, any>
 > extends NavigationEvent<Params> {
-	fetch(info: RequestInfo, init?: RequestInit): Promise<Response>;
+	fetch: typeof fetch;
 	data: Data;
 	setHeaders: (headers: Record<string, string>) => void;
 	parent: () => Promise<ParentData>;
@@ -368,6 +383,7 @@ export interface ServerLoadEvent<
 	ParentData extends Record<string, any> = Record<string, any>
 > extends RequestEvent<Params> {
 	parent: () => Promise<ParentData>;
+	depends: (...deps: string[]) => void;
 }
 
 export interface Action<
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index aebf0be71..29f84240d 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -298,7 +298,7 @@ export interface SSROptions {
 	public_env: Record<string, string>;
 	read(file: string): Buffer;
 	root: SSRComponent['default'];
-	service_worker?: string;
+	service_worker: boolean;
 	app_template({
 		head,
 		body,
diff --git a/packages/kit/types/private.d.ts b/packages/kit/types/private.d.ts
index 24b587ddc..36ed7cfbe 100644
--- a/packages/kit/types/private.d.ts
+++ b/packages/kit/types/private.d.ts
@@ -53,7 +53,13 @@ export interface AdapterEntry {
 
 export namespace Csp {
 	type ActionSource = 'strict-dynamic' | 'report-sample';
-	type BaseSource = 'self' | 'unsafe-eval' | 'unsafe-hashes' | 'unsafe-inline' | 'none';
+	type BaseSource =
+		| 'self'
+		| 'unsafe-eval'
+		| 'unsafe-hashes'
+		| 'unsafe-inline'
+		| 'wasm-unsafe-eval'
+		| 'none';
 	type CryptoSource = `${'nonce' | 'sha256' | 'sha384' | 'sha512'}-${string}`;
 	type FrameSource = HostSource | SchemeSource | 'self' | 'none';
 	type HostNameScheme = `${string}.${string}` | 'localhost';
@@ -195,7 +201,6 @@ export interface RequestOptions {
 
 export interface RouteDefinition {
 	id: string;
-	type: 'page' | 'endpoint';
 	pattern: RegExp;
 	segments: RouteSegment[];
 	methods: HttpMethod[];
@@ -208,3 +213,12 @@ export interface RouteSegment {
 }
 
 export type TrailingSlash = 'never' | 'always' | 'ignore';
+
+/**
+ * This doesn't actually exist, it's a way to better distinguish the type
+ */
+declare const uniqueSymbol: unique symbol;
+
+export interface UniqueInterface {
+	readonly [uniqueSymbol]: unknown;
+}
diff --git a/packages/package/CHANGELOG.md b/packages/package/CHANGELOG.md
index 0fbb8b088..2d6c7cb57 100644
--- a/packages/package/CHANGELOG.md
+++ b/packages/package/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/package
 
+## 1.0.0-next.5
+
+### Patch Changes
+
+- fix check for undefined on application/ strip ([#6932](https://github.com/sveltejs/kit/pull/6932))
+
 ## 1.0.0-next.4
 
 ### Patch Changes
diff --git a/packages/package/package.json b/packages/package/package.json
index b5bf86863..731fa5ec7 100644
--- a/packages/package/package.json
+++ b/packages/package/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/package",
-	"version": "1.0.0-next.4",
+	"version": "1.0.0-next.5",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/package/src/utils.js b/packages/package/src/utils.js
index 0b263857b..001970610 100644
--- a/packages/package/src/utils.js
+++ b/packages/package/src/utils.js
@@ -52,7 +52,7 @@ export function strip_lang_tags(content) {
 		.replace(
 			/(<!--[^]*?-->)|(<script[^>]*?)\s(?:type|lang)=(["'])(.*?)\3/g,
 			// things like application/ld+json should be kept as-is. Preprocessed languages are "ts" etc
-			(match, s1, s2, _, s4) => (s4.startsWith('application/') ? match : (s1 ?? '') + (s2 ?? ''))
+			(match, s1, s2, _, s4) => (s4?.startsWith('application/') ? match : (s1 ?? '') + (s2 ?? ''))
 		)
 		.replace(/(<!--[^]*?-->)|(<style[^>]*?)\s(?:type|lang)=(["']).*?\3/g, '$1$2');
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e7bbfd58e..f1a309a23 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -114,11 +114,12 @@ importers:
 
   packages/adapter-node:
     specifiers:
+      '@rollup/plugin-commonjs': ^22.0.1
       '@rollup/plugin-json': ^4.1.0
+      '@rollup/plugin-node-resolve': ^14.1.0
       '@sveltejs/kit': workspace:*
       '@types/node': ^16.11.36
       c8: ^7.11.3
-      esbuild: ^0.15.7
       node-fetch: ^3.2.4
       polka: ^1.0.0-next.22
       rimraf: ^3.0.2
@@ -127,16 +128,17 @@ importers:
       typescript: ^4.8.2
       uvu: ^0.5.3
     dependencies:
-      esbuild: 0.15.7
-    devDependencies:
+      '@rollup/plugin-commonjs': 22.0.1_rollup@2.78.1
       '@rollup/plugin-json': 4.1.0_rollup@2.78.1
+      '@rollup/plugin-node-resolve': 14.1.0_rollup@2.78.1
+      rollup: 2.78.1
+    devDependencies:
       '@sveltejs/kit': link:../kit
       '@types/node': 16.11.42
       c8: 7.11.3
       node-fetch: 3.2.6
       polka: 1.0.0-next.22
       rimraf: 3.0.2
-      rollup: 2.78.1
       sirv: 2.0.2
       typescript: 4.8.2
       uvu: 0.5.4
@@ -295,8 +297,8 @@ importers:
       uvu: ^0.5.3
       vite: ^3.1.1
     dependencies:
-      '@types/cookie': 0.5.1
       '@sveltejs/vite-plugin-svelte': 1.0.5_svelte@3.48.0+vite@3.1.1
+      '@types/cookie': 0.5.1
       cookie: 0.5.0
       devalue: 3.1.2
       kleur: 4.1.5
@@ -1085,9 +1087,8 @@ packages:
       glob: 7.2.3
       is-reference: 1.2.1
       magic-string: 0.25.9
-      resolve: 1.22.0
+      resolve: 1.22.1
       rollup: 2.78.1
-    dev: true
 
   /@rollup/plugin-json/4.1.0_rollup@2.78.1:
     resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==}
@@ -1096,7 +1097,6 @@ packages:
     dependencies:
       '@rollup/pluginutils': 3.1.0_rollup@2.78.1
       rollup: 2.78.1
-    dev: true
 
   /@rollup/plugin-node-resolve/14.1.0_rollup@2.78.1:
     resolution: {integrity: sha512-5G2niJroNCz/1zqwXtk0t9+twOSDlG00k1Wfd7bkbbXmwg8H8dvgHdIWAun53Ps/rckfvOC7scDBjuGFg5OaWw==}
@@ -1111,7 +1111,6 @@ packages:
       is-module: 1.0.0
       resolve: 1.22.1
       rollup: 2.78.1
-    dev: true
 
   /@rollup/pluginutils/3.1.0_rollup@2.78.1:
     resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
@@ -1123,7 +1122,6 @@ packages:
       estree-walker: 1.0.1
       picomatch: 2.3.1
       rollup: 2.78.1
-    dev: true
 
   /@rollup/pluginutils/4.2.1:
     resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
@@ -1183,11 +1181,9 @@ packages:
 
   /@types/estree/0.0.39:
     resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
-    dev: true
 
   /@types/estree/0.0.52:
     resolution: {integrity: sha512-BZWrtCU0bMVAIliIV+HJO1f1PR41M7NKjfxrFJwwhKI1KwhwOxYw1SXg9ao+CIMt774nFuGiG6eU+udtbEI9oQ==}
-    dev: true
 
   /@types/gitignore-parser/0.0.0:
     resolution: {integrity: sha512-qxOKILdhl4e639fWdkMySS4tBkRYHkrU2ZNScsMu84EPicliFRr+gAXCLPrs7kTFWdDpgAIlxtUr+YCRtVjsKw==}
@@ -1229,7 +1225,6 @@ packages:
 
   /@types/node/18.0.0:
     resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==}
-    dev: true
 
   /@types/normalize-package-data/2.4.1:
     resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -1253,7 +1248,6 @@ packages:
     resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
     dependencies:
       '@types/node': 18.0.0
-    dev: true
 
   /@types/sade/1.7.4:
     resolution: {integrity: sha512-6ys13kmtlY0aIOz4KtMdeBD9BHs6vSE3aRcj4vAZqXjypT2el8WZt6799CMjElVgh1cbOH/t3vrpQ4IpwytcPA==}
@@ -1504,7 +1498,6 @@ packages:
   /builtin-modules/3.3.0:
     resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
     engines: {node: '>=6'}
-    dev: true
 
   /c8/7.11.3:
     resolution: {integrity: sha512-6YBmsaNmqRm9OS3ZbIiL2EZgi1+Xc4O24jL3vMYGE6idixYuGdy76rIfIdltSKDj9DpLNrcXSonUTR1miBD0wA==}
@@ -1693,7 +1686,6 @@ packages:
 
   /commondir/1.0.1:
     resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
-    dev: true
 
   /concat-map/0.0.1:
     resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
@@ -2168,7 +2160,6 @@ packages:
 
   /estree-walker/1.0.1:
     resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
-    dev: true
 
   /estree-walker/2.0.2:
     resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -2622,7 +2613,6 @@ packages:
     engines: {node: '>=6'}
     dependencies:
       builtin-modules: 3.3.0
-    dev: true
 
   /is-callable/1.2.4:
     resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}
@@ -2641,12 +2631,6 @@ packages:
     dependencies:
       has: 1.0.3
 
-  /is-core-module/2.9.0:
-    resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==}
-    dependencies:
-      has: 1.0.3
-    dev: true
-
   /is-date-object/1.0.5:
     resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
     engines: {node: '>= 0.4'}
@@ -2682,7 +2666,6 @@ packages:
 
   /is-module/1.0.0:
     resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
-    dev: true
 
   /is-negative-zero/2.0.2:
     resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
@@ -2713,7 +2696,6 @@ packages:
     resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
     dependencies:
       '@types/estree': 0.0.52
-    dev: true
 
   /is-regex/1.1.4:
     resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
@@ -2922,7 +2904,6 @@ packages:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
       sourcemap-codec: 1.4.8
-    dev: true
 
   /magic-string/0.26.2:
     resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==}
@@ -3658,15 +3639,6 @@ packages:
     deprecated: https://github.com/lydell/resolve-url#deprecated
     dev: true
 
-  /resolve/1.22.0:
-    resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==}
-    hasBin: true
-    dependencies:
-      is-core-module: 2.9.0
-      path-parse: 1.0.7
-      supports-preserve-symlinks-flag: 1.0.0
-    dev: true
-
   /resolve/1.22.1:
     resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
     hasBin: true


Test patch:
diff --git a/packages/kit/test/apps/basics/.gitignore b/packages/kit/test/apps/basics/.gitignore
index fa2f5a579..fad4d3e15 100644
--- a/packages/kit/test/apps/basics/.gitignore
+++ b/packages/kit/test/apps/basics/.gitignore
@@ -1,2 +1,3 @@
 /test/errors.json
 !/.env
+/src/routes/routing/symlink-from
\ No newline at end of file
diff --git a/packages/kit/test/apps/basics/package.json b/packages/kit/test/apps/basics/package.json
index c3da79b25..76e9cf5b7 100644
--- a/packages/kit/test/apps/basics/package.json
+++ b/packages/kit/test/apps/basics/package.json
@@ -7,7 +7,7 @@
 		"build": "vite build",
 		"preview": "vite preview",
 		"check": "svelte-kit sync && tsc && svelte-check",
-		"test": "npm run test:dev && npm run test:build",
+		"test": "node test/setup.js && npm run test:dev && npm run test:build",
 		"test:dev": "rimraf test/errors.json && cross-env DEV=true playwright test",
 		"test:build": "rimraf test/errors.json && playwright test"
 	},
diff --git a/packages/kit/test/apps/basics/src/hooks.client.js b/packages/kit/test/apps/basics/src/hooks.client.js
index 62ee8c1eb..09bfd498b 100644
--- a/packages/kit/test/apps/basics/src/hooks.client.js
+++ b/packages/kit/test/apps/basics/src/hooks.client.js
@@ -1,4 +1,6 @@
 /** @type{import("@sveltejs/kit").HandleClientError} */
-export function handleError({ error }) {
-	return { message: /** @type {Error} */ (error).message };
+export function handleError({ error, event }) {
+	return event.url.pathname.endsWith('404-fallback')
+		? undefined
+		: { message: /** @type {Error} */ (error).message };
 }
diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js
index cb085363b..18f1c7a03 100644
--- a/packages/kit/test/apps/basics/src/hooks.server.js
+++ b/packages/kit/test/apps/basics/src/hooks.server.js
@@ -33,7 +33,7 @@ export const handleError = ({ event, error: e }) => {
 		: {};
 	errors[event.url.pathname] = error_to_pojo(error);
 	fs.writeFileSync('test/errors.json', JSON.stringify(errors));
-	return { message: error.message };
+	return event.url.pathname.endsWith('404-fallback') ? undefined : { message: error.message };
 };
 
 export const handle = sequence(
@@ -47,6 +47,15 @@ export const handle = sequence(
 		event.locals.name = /** @type {string} */ (event.cookies.get('name'));
 		return resolve(event);
 	},
+	async ({ event, resolve }) => {
+		if (event.url.pathname === '/cookies/serialize') {
+			event.cookies.set('before', 'before');
+			const response = await resolve(event);
+			response.headers.append('set-cookie', event.cookies.serialize('after', 'after'));
+			return response;
+		}
+		return resolve(event);
+	},
 	async ({ event, resolve }) => {
 		if (event.url.pathname === '/errors/error-in-handle') {
 			throw new Error('Error in handle');
diff --git a/packages/kit/test/apps/basics/src/routes/+layout.server.js b/packages/kit/test/apps/basics/src/routes/+layout.server.js
index cb10abf04..001350db5 100644
--- a/packages/kit/test/apps/basics/src/routes/+layout.server.js
+++ b/packages/kit/test/apps/basics/src/routes/+layout.server.js
@@ -1,6 +1,8 @@
-let should_fail = false;
+import { error, redirect } from '@sveltejs/kit';
+
+let should_fail = '';
 /**
- * @param {boolean} value
+ * @param {string} value
  */
 export function set_should_fail(value) {
 	should_fail = value;
@@ -8,8 +10,16 @@ export function set_should_fail(value) {
 
 export async function load() {
 	if (should_fail) {
-		set_should_fail(false);
-		throw new Error('Failed to load');
+		if (should_fail === 'expected') {
+			set_should_fail('');
+			throw error(401, 'Not allowed');
+		} else if (should_fail === 'unexpected') {
+			set_should_fail('');
+			throw new Error('Failed to load');
+		} else {
+			set_should_fail('');
+			throw redirect(307, '/load');
+		}
 	}
 	// Do NOT make this load function depend on something which would cause it to rerun
 	return {
diff --git a/packages/kit/test/apps/basics/src/routes/actions/redirect/+page.server.js b/packages/kit/test/apps/basics/src/routes/actions/redirect/+page.server.js
new file mode 100644
index 000000000..ff16ad14f
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/actions/redirect/+page.server.js
@@ -0,0 +1,15 @@
+import { redirect } from '@sveltejs/kit';
+
+/** @type {import('./$types').PageServerLoad} */
+export function load() {
+	return {
+		initial: 'initial'
+	};
+}
+
+/** @type {import('./$types').Actions} */
+export const actions = {
+	default: async () => {
+		throw redirect(303, '/actions/enhance');
+	}
+};
diff --git a/packages/kit/test/apps/basics/src/routes/actions/redirect/+page.svelte b/packages/kit/test/apps/basics/src/routes/actions/redirect/+page.svelte
new file mode 100644
index 000000000..7bcb55f72
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/actions/redirect/+page.svelte
@@ -0,0 +1,7 @@
+<script>
+	import { enhance } from '$app/forms';
+</script>
+
+<form method="post" use:enhance>
+	<button>Submit</button>
+</form>
diff --git a/packages/kit/test/apps/basics/src/routes/cookies/serialize/+server.js b/packages/kit/test/apps/basics/src/routes/cookies/serialize/+server.js
new file mode 100644
index 000000000..f46459823
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/cookies/serialize/+server.js
@@ -0,0 +1,8 @@
+/** @type {import('@sveltejs/kit').RequestHandler} */
+export const GET = ({ cookies }) => {
+	const response = new Response('success', {
+		status: 200
+	});
+	response.headers.append('set-cookie', cookies.serialize('endpoint', 'endpoint'));
+	return response;
+};
diff --git a/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte
index 2fda2e2e0..b8cf7fcdf 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte
@@ -1,8 +1,15 @@
 <script>
-	async function fail() {
-		await fetch('./error-html/make-root-fail');
-		location.reload();
+	/** @param {string} type */
+	async function fail(type) {
+		await fetch(`/errors/error-html/make-root-fail?type=${type}`);
+		if (type === 'redirect') {
+			location.assign(location.href + '/404');
+		} else {
+			location.reload();
+		}
 	}
 </script>
 
-<button on:click={fail}>Let's fail catastrophically</button>
+<button on:click={() => fail('unexpected')}>Unexpected</button>
+<button on:click={() => fail('expected')}>Expected</button>
+<button on:click={() => fail('redirect')}>Redirect</button>
diff --git a/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js b/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js
index 7c3f14cfd..c3030a9c9 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js
+++ b/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js
@@ -1,6 +1,9 @@
 import { set_should_fail } from '../../../+layout.server';
 
-export function GET() {
-	set_should_fail(true);
+/**
+ * @type {import('./$types').RequestHandler} param0
+ */
+export function GET({ url }) {
+	set_should_fail(url.searchParams.get('type'));
 	return new Response();
 }
diff --git a/packages/kit/test/apps/basics/src/routes/load/fetch-external-no-cookies/+page.js b/packages/kit/test/apps/basics/src/routes/load/fetch-external-no-cookies/+page.js
new file mode 100644
index 000000000..a968114e2
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/fetch-external-no-cookies/+page.js
@@ -0,0 +1,7 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ fetch, url }) {
+	const port = url.searchParams.get('port');
+	const res = await fetch(`http://localhost:${port}`);
+
+	return await res.json();
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/fetch-external-no-cookies/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/fetch-external-no-cookies/+page.svelte
new file mode 100644
index 000000000..6e7c1e6ac
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/fetch-external-no-cookies/+page.svelte
@@ -0,0 +1,1 @@
+<h1>hello</h1>
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.js
new file mode 100644
index 000000000..2d4c6d175
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.js
@@ -0,0 +1,8 @@
+/** @type {import('./$types').PageLoad} */
+export function load({ data, depends }) {
+	depends('invalidate-depends:shared');
+	return {
+		shared: new Date().getTime(),
+		...data
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.server.js
new file mode 100644
index 000000000..fb81ddb99
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.server.js
@@ -0,0 +1,7 @@
+/** @type {import('./$types').PageServerLoad} */
+export function load({ depends }) {
+	depends('invalidate-depends:server');
+	return {
+		server: new Date().getTime()
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.svelte
new file mode 100644
index 000000000..1ce33105c
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/depends/+page.svelte
@@ -0,0 +1,24 @@
+<script>
+	import { invalidate } from '$app/navigation';
+
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<p class="shared">{data.shared}</p>
+<button
+	type="button"
+	class="shared"
+	on:click={() => invalidate('invalidate-depends:shared')}
+>
+	invalidate shared
+</button>
+
+<p class="server">{data.server}</p>
+<button
+	type="button"
+	class="server"
+	on:click={() => invalidate('invalidate-depends:server')}
+>
+	invalidate server
+</button>
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+layout.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+layout.js
new file mode 100644
index 000000000..612afdb02
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+layout.js
@@ -0,0 +1,24 @@
+import { redirect } from '@sveltejs/kit';
+import { get } from 'svelte/store';
+import { get_layout, redirect_state } from './state';
+
+/** @type {import('./$types').LayoutLoad} */
+export function load({ depends }) {
+	depends('invalid:layout');
+
+	if (get(redirect_state) === 'running') {
+		redirect_state.set('done');
+		throw redirect(307, '/load/invalidation/multiple/redirect');
+	}
+
+	return new Promise((resolve) =>
+		setTimeout(
+			() =>
+				resolve({
+					count_layout: get_layout(),
+					redirect_mode: get(redirect_state)
+				}),
+			Math.random() * 500
+		)
+	);
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+layout.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+layout.svelte
new file mode 100644
index 000000000..f9cc3bd4b
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+layout.svelte
@@ -0,0 +1,29 @@
+<script>
+	import { invalidate, invalidateAll } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { increment_layout, increment_page } from './state';
+
+	/** @param {string} str */
+	async function update(str) {
+		if (str !== 'page') {
+			increment_layout();
+		}
+		if (str !== 'layout') {
+			increment_page();
+		}
+
+		if (str === 'all') {
+			invalidateAll();
+		} else {
+			invalidate(`invalid:${str}`);
+		}
+	}
+</script>
+
+<button class="layout" on:click={() => update('layout')}>Invalidate layout</button>
+<button class="page" on:click={() => update('page')}>Invalidate page</button>
+<button class="all" on:click={() => update('all')}>Invalidate all</button>
+
+<p>layout: {$page.data.count_layout}, page: {$page.data.count_page}</p>
+
+<slot />
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+page.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+page.js
new file mode 100644
index 000000000..3182fe7ef
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+page.js
@@ -0,0 +1,15 @@
+import { get_page } from './state';
+
+/** @type {import('./$types').PageLoad} */
+export function load({ depends }) {
+	depends('invalid:page');
+	return new Promise((resolve) =>
+		setTimeout(
+			() =>
+				resolve({
+					count_page: get_page()
+				}),
+			Math.random() * 500
+		)
+	);
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/+page.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/redirect/+page.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/redirect/+page.js
new file mode 100644
index 000000000..db2ffca2a
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/redirect/+page.js
@@ -0,0 +1,14 @@
+import { redirect } from '@sveltejs/kit';
+import { redirect_state } from '../state';
+
+/** @type {import('./$types').PageLoad} */
+export async function load({ parent }) {
+	const { redirect_mode } = await parent();
+	if (redirect_mode === 'start') {
+		redirect_state.set('running');
+		throw redirect(307, '/load/invalidation/multiple');
+	}
+	if (redirect_mode === 'running') {
+		throw new Error('Shouldnt get redirected here with state "running"');
+	}
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/redirect/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/redirect/+page.svelte
new file mode 100644
index 000000000..bb8eb9ccb
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/redirect/+page.svelte
@@ -0,0 +1,12 @@
+<script>
+	import { invalidateAll } from '$app/navigation';
+	import { redirect_state } from '../state';
+
+	function redirect() {
+		redirect_state.set('start');
+		invalidateAll();
+	}
+</script>
+
+<button class="redirect" on:click={redirect}>redirect</button>
+<p class="redirect-state">Redirect state: {$redirect_state}</p>
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/state.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/state.js
new file mode 100644
index 000000000..3d1ca87a3
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/multiple/state.js
@@ -0,0 +1,22 @@
+import { writable } from 'svelte/store';
+
+let count_layout = 0;
+let count_page = 0;
+
+export const redirect_state = writable('initial');
+
+export function increment_layout() {
+	count_layout++;
+}
+
+export function increment_page() {
+	count_page++;
+}
+
+export function get_layout() {
+	return count_layout;
+}
+
+export function get_page() {
+	return count_page;
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+layout.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+layout.js
new file mode 100644
index 000000000..1d25f6234
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+layout.js
@@ -0,0 +1,7 @@
+/** @type {import('./$types').LayoutLoad} */
+export function load({ params }) {
+	return {
+		a: params.a,
+		b: params.b
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+layout.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+layout.svelte
new file mode 100644
index 000000000..9b3472274
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+layout.svelte
@@ -0,0 +1,14 @@
+<script>
+	/** @type {import('./$types').LayoutData} */
+	export let data;
+</script>
+
+<slot />
+
+<pre>{JSON.stringify({
+		a: data.a,
+		b: data.b
+	})}</pre>
+
+<a href="/load/invalidation/params/1">1</a>
+<a href="/load/invalidation/params/1/x">1/x</a>
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/+page.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/params/[a]/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/[a]/+page.svelte
new file mode 100644
index 000000000..465276da1
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/[a]/+page.svelte
@@ -0,0 +1,1 @@
+<h1>[a]</h1>
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/params/[a]/[b]/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/[a]/[b]/+page.svelte
new file mode 100644
index 000000000..956ca34c9
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/params/[a]/[b]/+page.svelte
@@ -0,0 +1,1 @@
+<h1>[a]/[b]</h1>
diff --git a/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.server.js
new file mode 100644
index 000000000..01e1a121a
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.server.js
@@ -0,0 +1,6 @@
+/** @type {import('./$types').PageServerLoad} */
+export function load({ url }) {
+	return {
+		x: url.searchParams.get('x')
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.svelte
new file mode 100644
index 000000000..9dc41ad6f
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.svelte
@@ -0,0 +1,7 @@
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>x: {data.x}</h1>
+<a href="/load/server-data-nostore?x=2">2</a>
diff --git a/packages/kit/test/apps/basics/src/routes/routing/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/+page.svelte
index 61f62ec6f..1d300bf63 100644
--- a/packages/kit/test/apps/basics/src/routes/routing/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/routing/+page.svelte
@@ -6,6 +6,7 @@
 
 <a href="/routing/a">a</a>
 <a href="/routing/ambiguous/ok.json" rel="external">ok</a>
+<a href="/routing/symlink-from">symlinked</a>
 <a href="http://localhost:{$page.url.searchParams.get('port')}">elsewhere</a>
 <a href="/static.json">static.json</a>
 
diff --git a/packages/kit/test/apps/basics/src/routes/routing/endpoint-next-to-page/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/endpoint-next-to-page/+page.svelte
new file mode 100644
index 000000000..b88ab6af7
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/endpoint-next-to-page/+page.svelte
@@ -0,0 +1,18 @@
+<script>
+	let result;
+
+	/** @param {string} method */
+	async function request(method) {
+		result = 'loading';
+		const response = await fetch('/routing/endpoint-next-to-page', {method});
+		result = await response.text();
+	}
+</script>
+
+<p>Hi</p>
+<button on:click={() => request('GET')}>GET</button>
+<button on:click={() => request('PUT')}>PUT</button>
+<button on:click={() => request('PATCH')}>PATCH</button>
+<button on:click={() => request('POST')}>POST</button>
+<button on:click={() => request('DELETE')}>DELETE</button>
+<pre>{result}</pre>
diff --git a/packages/kit/test/apps/basics/src/routes/routing/endpoint-next-to-page/+server.js b/packages/kit/test/apps/basics/src/routes/routing/endpoint-next-to-page/+server.js
new file mode 100644
index 000000000..007c90f0d
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/endpoint-next-to-page/+server.js
@@ -0,0 +1,24 @@
+/** @type {import('./$types').RequestHandler} */
+export function GET() {
+	return new Response('GET');
+}
+
+/** @type {import('./$types').RequestHandler} */
+export function PUT() {
+	return new Response('PUT');
+}
+
+/** @type {import('./$types').RequestHandler} */
+export function PATCH() {
+	return new Response('PATCH');
+}
+
+/** @type {import('./$types').RequestHandler} */
+export function POST() {
+	return new Response('POST');
+}
+
+/** @type {import('./$types').RequestHandler} */
+export function DELETE() {
+	return new Response('DELETE');
+}
diff --git a/packages/kit/test/apps/basics/src/routes/routing/symlink-to/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/symlink-to/+page.svelte
new file mode 100644
index 000000000..d80b82ddf
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/symlink-to/+page.svelte
@@ -0,0 +1,1 @@
+<h1>symlinked</h1>
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index cf12fdc98..9f8a5ed62 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -8,7 +8,7 @@ test.skip(({ javaScriptEnabled }) => !javaScriptEnabled);
 test.describe.configure({ mode: 'parallel' });
 
 test.describe('beforeNavigate', () => {
-	test('prevents navigation triggered by link click', async ({ clicknav, page, baseURL }) => {
+	test('prevents navigation triggered by link click', async ({ page, baseURL }) => {
 		await page.goto('/before-navigate/prevent-navigation');
 
 		await page.click('[href="/before-navigate/a"]');
@@ -263,7 +263,7 @@ test.describe('Endpoints', () => {
 	});
 });
 
-test.describe('Errors', () => {
+test.describe.serial('Errors', () => {
 	test('client-side load errors', async ({ page }) => {
 		await page.goto('/errors/load-client');
 
@@ -302,14 +302,29 @@ test.describe('Errors', () => {
 		expect(await page.innerHTML('h1')).toBe('401');
 	});
 
-	test('Root error falls back to error.html', async ({ page }) => {
+	test('Root error falls back to error.html (unexpected error)', async ({ page }) => {
 		await page.goto('/errors/error-html');
-		await page.click('button');
+		await page.click('button:text-is("Unexpected")');
 		expect(await page.textContent('h1')).toBe('Error - 500');
 		expect(await page.textContent('p')).toBe(
 			'This is the static error page with the following message: Failed to load'
 		);
 	});
+
+	test('Root error falls back to error.html (expected error)', async ({ page }) => {
+		await page.goto('/errors/error-html');
+		await page.click('button:text-is("Expected")');
+		expect(await page.textContent('h1')).toBe('Error - 401');
+		expect(await page.textContent('p')).toBe(
+			'This is the static error page with the following message: Not allowed'
+		);
+	});
+
+	test('Root 404 redirects somewhere due to root layout', async ({ page, baseURL }) => {
+		await page.goto('/errors/error-html');
+		await Promise.all([page.waitForNavigation(), page.click('button:text-is("Redirect")')]);
+		expect(page.url()).toBe(baseURL + '/load');
+	});
 });
 
 test.describe('Load', () => {
@@ -416,6 +431,17 @@ test.describe('Load', () => {
 		await expect(page.locator('p')).toHaveText('Count is 2');
 	});
 
+	test('__data.js has cache-control: private, no-store', async ({ page, clicknav }) => {
+		await page.goto('/load/server-data-nostore?x=1');
+
+		const [response] = await Promise.all([
+			page.waitForResponse((response) => /__data\.js/.test(response.url())),
+			clicknav('[href="/load/server-data-nostore?x=2"]')
+		]);
+
+		expect(response.headers()['cache-control']).toBe('private, no-store');
+	});
+
 	if (process.env.DEV) {
 		test('using window.fetch causes a warning', async ({ page }) => {
 			const port = 5173;
@@ -734,6 +760,72 @@ test.describe.serial('Invalidation', () => {
 		await page.waitForTimeout(200);
 		expect(await page.textContent('h1')).toBe('a: 4, b: 5');
 	});
+
+	test('multiple invalidations run concurrently', async ({ page, request }) => {
+		await page.goto('/load/invalidation/multiple');
+		expect(await page.textContent('p')).toBe('layout: 0, page: 0');
+
+		await page.click('button.layout');
+		await page.click('button.layout');
+		await page.click('button.page');
+		await page.click('button.page');
+		await page.click('button.layout');
+		await page.click('button.page');
+		await page.click('button.all');
+		await expect(page.locator('p')).toHaveText('layout: 4, page: 4');
+	});
+
+	test('invalidateAll persists through redirects', async ({ page }) => {
+		await page.goto('/load/invalidation/multiple/redirect');
+		await page.click('button.redirect');
+		await expect(page.locator('p.redirect-state')).toHaveText('Redirect state: done');
+	});
+
+	test('+layout(.server).js is re-run when server dep is invalidated', async ({ page }) => {
+		await page.goto('/load/invalidation/depends');
+		const server = await page.textContent('p.server');
+		const shared = await page.textContent('p.shared');
+		expect(server).toBeDefined();
+		expect(shared).toBeDefined();
+
+		await Promise.all([page.click('button.server'), page.waitForLoadState('networkidle')]);
+		await page.waitForTimeout(200);
+		const next_server = await page.textContent('p.server');
+		const next_shared = await page.textContent('p.shared');
+		expect(server).not.toBe(next_server);
+		expect(shared).not.toBe(next_shared);
+	});
+
+	test('+layout.js is re-run when shared dep is invalidated', async ({ page }) => {
+		await page.goto('/load/invalidation/depends');
+		const server = await page.textContent('p.server');
+		const shared = await page.textContent('p.shared');
+		expect(server).toBeDefined();
+		expect(shared).toBeDefined();
+
+		await Promise.all([page.click('button.shared'), page.waitForLoadState('networkidle')]);
+		await page.waitForTimeout(200);
+		const next_server = await page.textContent('p.server');
+		const next_shared = await page.textContent('p.shared');
+		expect(server).toBe(next_server);
+		expect(shared).not.toBe(next_shared);
+	});
+
+	test('Parameter use is tracked even for routes that do not use the parameters', async ({
+		page,
+		clicknav
+	}) => {
+		await page.goto('/load/invalidation/params');
+
+		await clicknav('[href="/load/invalidation/params/1"]');
+		expect(await page.textContent('pre')).toBe('{"a":"1"}');
+
+		await clicknav('[href="/load/invalidation/params/1/x"]');
+		expect(await page.textContent('pre')).toBe('{"a":"1","b":"x"}');
+
+		await page.goBack();
+		expect(await page.textContent('pre')).toBe('{"a":"1"}');
+	});
 });
 
 test.describe('data-sveltekit attributes', () => {
@@ -807,3 +899,14 @@ test.describe('data-sveltekit attributes', () => {
 		expect(await page.evaluate(() => window.scrollY)).toBe(0);
 	});
 });
+
+test('+server.js next to +page.svelte works', async ({ page }) => {
+	await page.goto('/routing/endpoint-next-to-page');
+	expect(await page.textContent('p')).toBe('Hi');
+
+	for (const method of ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) {
+		page.click(`button:has-text("${method}")`);
+		await page.waitForResponse('/routing/endpoint-next-to-page');
+		expect(await page.textContent('pre')).toBe(method);
+	}
+});
diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js
index 0404859e5..59f1c00ec 100644
--- a/packages/kit/test/apps/basics/test/server.test.js
+++ b/packages/kit/test/apps/basics/test/server.test.js
@@ -1,5 +1,5 @@
 import { expect } from '@playwright/test';
-import { test } from '../../../utils.js';
+import { start_server, test } from '../../../utils.js';
 import { fetch } from 'undici';
 import { createHash, randomBytes } from 'node:crypto';
 
@@ -23,6 +23,29 @@ test.describe('Content-Type', () => {
 	});
 });
 
+test.describe('Cookies', () => {
+	test('does not forward cookies from external domains', async ({ request }) => {
+		const { close, port } = await start_server(async (req, res) => {
+			if (req.url === '/') {
+				res.writeHead(200, {
+					'set-cookie': 'external=true',
+					'access-control-allow-origin': '*'
+				});
+
+				res.end('ok');
+			} else {
+				res.writeHead(404);
+				res.end('not found');
+			}
+		});
+
+		const response = await request.get(`/load/fetch-external-no-cookies?port=${port}`);
+		expect(response.headers()['set-cookie']).not.toContain('external=true');
+
+		close();
+	});
+});
+
 test.describe('CSRF', () => {
 	test('Blocks requests with incorrect origin', async ({ baseURL }) => {
 		const res = await fetch(`${baseURL}/csrf`, {
@@ -336,6 +359,18 @@ test.describe('setHeaders', () => {
 	});
 });
 
+test.describe('cookies', () => {
+	test('cookie.serialize created correct cookie header string', async ({ page }) => {
+		const response = await page.goto('/cookies/serialize');
+		const cookies = await response.headerValue('set-cookie');
+		expect(
+			cookies.includes('before=before') &&
+				cookies.includes('after=after') &&
+				cookies.includes('endpoint=endpoint')
+		).toBe(true);
+	});
+});
+
 test.describe('Miscellaneous', () => {
 	test('does not serve version.json with an immutable cache header', async ({ request }) => {
 		// this isn't actually a great test, because caching behaviour is down to adapters.
diff --git a/packages/kit/test/apps/basics/test/setup.js b/packages/kit/test/apps/basics/test/setup.js
new file mode 100644
index 000000000..9be580053
--- /dev/null
+++ b/packages/kit/test/apps/basics/test/setup.js
@@ -0,0 +1,7 @@
+import fs from 'fs';
+
+if (process.platform !== 'win32') {
+	process.chdir('src/routes/routing');
+	fs.rmSync('symlink-from', { recursive: true, force: true });
+	fs.symlinkSync('symlink-to', 'symlink-from', 'dir');
+}
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 15c03f300..9332b0825 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -1633,6 +1633,21 @@ test.describe('Routing', () => {
 		await page.goto('/static');
 		expect(await page.textContent('h1')).toBe('hello');
 	});
+
+	test('shows "Not Found" in 404 case', async ({ page }) => {
+		await page.goto('/404-fallback');
+		expect(await page.textContent('h1')).toBe('404');
+		expect(await page.textContent('p')).toBe('This is your custom error page saying: "Not Found"');
+	});
+
+	if (process.platform !== 'win32') {
+		test('Respects symlinks', async ({ page, clicknav }) => {
+			await page.goto('/routing');
+			await clicknav('[href="/routing/symlink-from"]');
+
+			expect(await page.textContent('h1')).toBe('symlinked');
+		});
+	}
 });
 
 test.describe('Matchers', () => {
@@ -1801,7 +1816,7 @@ test.describe('Actions', () => {
 		}
 	});
 
-	test('use:enhance', async ({ page, app }) => {
+	test('use:enhance', async ({ page }) => {
 		await page.goto('/actions/enhance');
 
 		expect(await page.textContent('pre')).toBe(JSON.stringify(null));
@@ -1845,6 +1860,17 @@ test.describe('Actions', () => {
 
 		await expect(page.locator('pre')).toHaveText(JSON.stringify({ result: 'register: foo' }));
 	});
+
+	test('redirect', async ({ page }) => {
+		await page.goto('/actions/redirect');
+
+		page.click('button');
+
+		await page.waitForResponse('/actions/redirect');
+		await page.waitForTimeout(50);
+
+		expect(page.url()).toContain('/actions/enhance');
+	});
 });
 
 test.describe('Cookies API', () => {
diff --git a/packages/kit/test/prerendering/basics/src/service-worker.js b/packages/kit/test/prerendering/basics/src/service-worker.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js
index 44d80bfff..a7135660a 100644
--- a/packages/kit/test/prerendering/basics/test/test.js
+++ b/packages/kit/test/prerendering/basics/test/test.js
@@ -225,4 +225,9 @@ test('prerenders a page in a (group)', () => {
 	assert.ok(content.includes('<h1>grouped</h1>'));
 });
 
+test('injects relative service worker', () => {
+	const content = read('index.html');
+	assert.ok(content.includes(`navigator.serviceWorker.register('./service-worker.js')`));
+});
+
 test.run();