Repository: sveltejs/kit
Pull request #9309 (2023-03-03 22:48:34)

docs: update adapter-node.md
Fix the ```npm ci``` command line for actual version
Extend ORIGIN samples for local development

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [ ] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [ ] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [ ] 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. Changesets that add features should be `minor` and those that fix bugs should be `patch`. Please prefix changeset messages with `feat:`, `fix:`, or `chore:`.



Patch:
diff --git a/.changeset/eighty-dots-watch.md b/.changeset/eighty-dots-watch.md
new file mode 100644
index 000000000..7e4a417c8
--- /dev/null
+++ b/.changeset/eighty-dots-watch.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: remove buggy cookie path detection
diff --git a/.changeset/loud-ghosts-worry.md b/.changeset/loud-ghosts-worry.md
new file mode 100644
index 000000000..182e94259
--- /dev/null
+++ b/.changeset/loud-ghosts-worry.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': fix
+---
+
+fix: don't skip required parameters after missing optional parameters
diff --git a/.changeset/perfect-brooms-hope.md b/.changeset/perfect-brooms-hope.md
new file mode 100644
index 000000000..428dbff68
--- /dev/null
+++ b/.changeset/perfect-brooms-hope.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': patch
+---
+
+fix: add `BODY_SIZE_LIMIT` to the list of recognized env variables
diff --git a/.changeset/strange-garlics-pump.md b/.changeset/strange-garlics-pump.md
new file mode 100644
index 000000000..54207bbc4
--- /dev/null
+++ b/.changeset/strange-garlics-pump.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: deal with fast consecutive promise resolutions when streaming
diff --git a/documentation/docs/25-build-and-deploy/40-adapter-node.md b/documentation/docs/25-build-and-deploy/40-adapter-node.md
index f8af999a1..bbb8fa5d3 100644
--- a/documentation/docs/25-build-and-deploy/40-adapter-node.md
+++ b/documentation/docs/25-build-and-deploy/40-adapter-node.md
@@ -24,7 +24,7 @@ export default {
 
 First, build your app with `npm run build`. This will create the production server in the output directory specified in the adapter options, defaulting to `build`.
 
-You will need the output directory, the project's `package.json`, and the production dependencies in `node_modules` to run the application. Production dependencies can be generated with `npm ci --prod` (you can skip this step if your app doesn't have any dependencies). You can then start your app with this command:
+You will need the output directory, the project's `package.json`, and the production dependencies in `node_modules` to run the application. Production dependencies can be generated by copying the `package.json` and `package-lock.json` and then running `npm ci --omit dev` (you can skip this step if your app doesn't have any dependencies). You can then start your app with this command:
 
 ```bash
 node build
@@ -63,6 +63,9 @@ HTTP doesn't give SvelteKit a reliable way to know the URL that is currently bei
 
 ```
 ORIGIN=https://my.site node build
+
+# or e.g. for local previewing and testing
+ORIGIN=http://localhost:3000 node build
 ```
 
 With this, a request for the `/stuff` pathname will correctly resolve to `https://my.site/stuff`. Alternatively, you can specify headers that tell SvelteKit about the request protocol and host, from which it can construct the origin URL:
diff --git a/package.json b/package.json
index 31fa29573..260162c8b 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
 		"tiny-glob": "^0.2.9",
 		"typescript": "^4.9.4"
 	},
-	"packageManager": "pnpm@7.28.0",
+	"packageManager": "pnpm@7.29.0",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/adapter-node/src/env.js b/packages/adapter-node/src/env.js
index 2f55c26b3..841bddf2c 100644
--- a/packages/adapter-node/src/env.js
+++ b/packages/adapter-node/src/env.js
@@ -8,7 +8,8 @@ const expected = new Set([
 	'XFF_DEPTH',
 	'ADDRESS_HEADER',
 	'PROTOCOL_HEADER',
-	'HOST_HEADER'
+	'HOST_HEADER',
+	'BODY_SIZE_LIMIT'
 ]);
 
 if (ENV_PREFIX) {
diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md
index 8fae3bba5..db308d31c 100644
--- a/packages/create-svelte/CHANGELOG.md
+++ b/packages/create-svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
 # create-svelte
 
+## 3.1.1
+
+### Patch Changes
+
+- chore: Add hint for toggling additional options during scaffolding flow. ([#9318](https://github.com/sveltejs/kit/pull/9318))
+
 ## 3.1.0
 
 ### Minor Changes
diff --git a/packages/create-svelte/bin.js b/packages/create-svelte/bin.js
index f9e109b5e..04a61ae16 100755
--- a/packages/create-svelte/bin.js
+++ b/packages/create-svelte/bin.js
@@ -77,7 +77,7 @@ const options = await p.group(
 
 		features: () =>
 			p.multiselect({
-				message: 'Select additional options',
+				message: 'Select additional options (use arrow keys/space bar)',
 				required: false,
 				options: [
 					{
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index 8f70c3160..747f803a4 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "create-svelte",
-	"version": "3.1.0",
+	"version": "3.1.1",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 475cdf844..cb18c4c42 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,15 @@
 # @sveltejs/kit
 
+## 1.10.1
+
+### Patch Changes
+
+- fix: respect `<base>` when crawling ([#9257](https://github.com/sveltejs/kit/pull/9257))
+
+- fix: scroll before resetting focus, to avoid flash of unscrolled content ([#9311](https://github.com/sveltejs/kit/pull/9311))
+
+- fix: omit hash from global during development ([#9310](https://github.com/sveltejs/kit/pull/9310))
+
 ## 1.10.0
 
 ### Minor Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 83ed1c1b0..d76f7bf6f 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.10.0",
+	"version": "1.10.1",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/core/postbuild/crawl.js b/packages/kit/src/core/postbuild/crawl.js
index fd1bd4c9e..4132ba6c1 100644
--- a/packages/kit/src/core/postbuild/crawl.js
+++ b/packages/kit/src/core/postbuild/crawl.js
@@ -1,3 +1,4 @@
+import { resolve } from '../../utils/url.js';
 import { decode } from './entities.js';
 
 const DOCTYPE = 'DOCTYPE';
@@ -12,8 +13,11 @@ const ATTRIBUTE_NAME = /[^\t\n\f />"'=]/;
 
 const WHITESPACE = /[\s\n\r]/;
 
-/** @param {string} html */
-export function crawl(html) {
+/**
+ * @param {string} html
+ * @param {string} base
+ */
+export function crawl(html, base) {
 	/** @type {string[]} */
 	const ids = [];
 
@@ -157,7 +161,11 @@ export function crawl(html) {
 							value = decode(value);
 
 							if (name === 'href') {
-								href = value;
+								if (tag === 'BASE') {
+									base = resolve(base, value);
+								} else {
+									href = resolve(base, value);
+								}
 							} else if (name === 'id') {
 								ids.push(value);
 							} else if (name === 'name') {
@@ -165,7 +173,7 @@ export function crawl(html) {
 							} else if (name === 'rel') {
 								rel = value;
 							} else if (name === 'src') {
-								if (value) hrefs.push(value);
+								if (value) hrefs.push(resolve(base, value));
 							} else if (name === 'srcset') {
 								const candidates = [];
 								let insideURL = true;
@@ -183,7 +191,7 @@ export function crawl(html) {
 								candidates.push(value);
 								for (const candidate of candidates) {
 									const src = candidate.split(WHITESPACE)[0];
-									if (src) hrefs.push(src);
+									if (src) hrefs.push(resolve(base, src));
 								}
 							}
 						} else {
@@ -195,7 +203,7 @@ export function crawl(html) {
 				}
 
 				if (href && !/\bexternal\b/i.test(rel)) {
-					hrefs.push(href);
+					hrefs.push(resolve(base, href));
 				}
 			}
 		}
diff --git a/packages/kit/src/core/postbuild/crawl.spec.js b/packages/kit/src/core/postbuild/crawl.spec.js
index bad1a21e7..c62b15552 100644
--- a/packages/kit/src/core/postbuild/crawl.spec.js
+++ b/packages/kit/src/core/postbuild/crawl.spec.js
@@ -13,7 +13,7 @@ for (const fixture of fs.readdirSync(fixtures)) {
 
 		// const start = Date.now();
 
-		const output = crawl(input);
+		const output = crawl(input, '/');
 
 		// uncomment to see how long it took
 		// console.error(fixture, Date.now() - start);
diff --git a/packages/kit/src/core/postbuild/fixtures/base/input.html b/packages/kit/src/core/postbuild/fixtures/base/input.html
new file mode 100644
index 000000000..82a5aa945
--- /dev/null
+++ b/packages/kit/src/core/postbuild/fixtures/base/input.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<base href="/base-path/" />
+		<link rel="stylesheet" href="styles.css" />
+		<link rel="icon" href="favicon.png" />
+	</head>
+	<body>
+		<a href="https://external.com">https://external.com</a>
+		<a href="wheee">/wheee</a>
+		<a data-sveltekit-prefetch href="wheee2">/wheee</a>
+		<a href="wheee3" data-sveltekit-prefetch>/wheee</a>
+		<a href="wheee4">/wheee</a>
+	</body>
+</html>
diff --git a/packages/kit/src/core/postbuild/fixtures/base/output.json b/packages/kit/src/core/postbuild/fixtures/base/output.json
new file mode 100644
index 000000000..af7a9e70a
--- /dev/null
+++ b/packages/kit/src/core/postbuild/fixtures/base/output.json
@@ -0,0 +1,12 @@
+{
+	"hrefs": [
+		"/base-path/styles.css",
+		"/base-path/favicon.png",
+		"https://external.com",
+		"/base-path/wheee",
+		"/base-path/wheee2",
+		"/base-path/wheee3",
+		"/base-path/wheee4"
+	],
+	"ids": []
+}
diff --git a/packages/kit/src/core/postbuild/fixtures/basic-srcset/output.json b/packages/kit/src/core/postbuild/fixtures/basic-srcset/output.json
index cf0e24ae4..536c3d3f3 100644
--- a/packages/kit/src/core/postbuild/fixtures/basic-srcset/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/basic-srcset/output.json
@@ -1,11 +1,11 @@
 {
 	"hrefs": [
-		"header.png",
+		"/header.png",
 		"https://example.com/w_200,q_100/header.png",
-		"header640.png",
-		"header960.png",
-		"header1024.png",
-		"header.png"
+		"/header640.png",
+		"/header960.png",
+		"/header1024.png",
+		"/header.png"
 	],
 	"ids": []
 }
diff --git a/packages/kit/src/core/postbuild/fixtures/ids/output.json b/packages/kit/src/core/postbuild/fixtures/ids/output.json
index 1ad4d8f3f..e749d3128 100644
--- a/packages/kit/src/core/postbuild/fixtures/ids/output.json
+++ b/packages/kit/src/core/postbuild/fixtures/ids/output.json
@@ -1,4 +1,4 @@
 {
-	"hrefs": ["#encöded"],
+	"hrefs": ["/#encöded"],
 	"ids": ["before", "after", "encöded"]
 }
diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js
index cdfaacb0d..bd938baf8 100644
--- a/packages/kit/src/core/postbuild/prerender.js
+++ b/packages/kit/src/core/postbuild/prerender.js
@@ -240,17 +240,14 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) {
 		const headers = Object.fromEntries(response.headers);
 
 		if (config.prerender.crawl && headers['content-type'] === 'text/html') {
-			const { ids, hrefs } = crawl(body.toString());
+			const { ids, hrefs } = crawl(body.toString(), decoded);
 
 			actual_hashlinks.set(decoded, ids);
 
 			for (const href of hrefs) {
-				if (href.startsWith('data:')) continue;
+				if (!is_root_relative(href)) continue;
 
-				const resolved = resolve(encoded, href);
-				if (!is_root_relative(resolved)) continue;
-
-				const { pathname, search, hash } = new URL(resolved, 'http://localhost');
+				const { pathname, search, hash } = new URL(href, 'http://localhost');
 
 				if (search) {
 					// TODO warn that query strings have no effect on statically-exported pages
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 569064bb9..028d873e3 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -335,7 +335,10 @@ function kit({ svelte_config }) {
 
 		async load(id, options) {
 			const browser = !options?.ssr;
-			const global = `globalThis.__sveltekit_${version_hash}`;
+
+			const global = is_build
+				? `globalThis.__sveltekit_${version_hash}`
+				: `globalThis.__sveltekit_dev`;
 
 			if (options?.ssr === false && process.env.TEST !== 'true') {
 				const normalized_cwd = vite.normalizePath(cwd);
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 11f83e40b..f4c12fe23 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -379,17 +379,7 @@ export function create_client(app, target) {
 			// need to render the DOM before we can scroll to the rendered elements and do focus management
 			await tick();
 
-			const changed_focus =
-				// reset focus only if any manual focus management didn't override it
-				document.activeElement !== activeElement &&
-				// also refocus when activeElement is body already because the
-				// focus event might not have been fired on it yet
-				document.activeElement !== document.body;
-
-			if (!keepfocus && !changed_focus) {
-				await reset_focus();
-			}
-
+			// we reset scroll before dealing with focus, to avoid a flash of unscrolled content
 			if (autoscroll) {
 				const deep_linked =
 					url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)));
@@ -404,6 +394,17 @@ export function create_client(app, target) {
 					scrollTo(0, 0);
 				}
 			}
+
+			const changed_focus =
+				// reset focus only if any manual focus management didn't override it
+				document.activeElement !== activeElement &&
+				// also refocus when activeElement is body already because the
+				// focus event might not have been fired on it yet
+				document.activeElement !== document.body;
+
+			if (!keepfocus && !changed_focus) {
+				await reset_focus();
+			}
 		} else {
 			await tick();
 		}
diff --git a/packages/kit/src/runtime/server/cookie.js b/packages/kit/src/runtime/server/cookie.js
index c1bb5adec..f8d872207 100644
--- a/packages/kit/src/runtime/server/cookie.js
+++ b/packages/kit/src/runtime/server/cookie.js
@@ -27,26 +27,6 @@ export function get_cookies(request, url, trailing_slash) {
 	// Emulate browser-behavior: if the cookie is set at '/foo/bar', its path is '/foo'
 	const default_path = normalized_url.split('/').slice(0, -1).join('/') || '/';
 
-	if (__SVELTEKIT_DEV__) {
-		// TODO this could theoretically be wrong if the cookie was set unencoded?
-		const initial_decoded_cookies = parse(header, { decode: decodeURIComponent });
-		// Remove all cookies that no longer exist according to the request
-		for (const name of Object.keys(cookie_paths)) {
-			cookie_paths[name] = new Set(
-				[...cookie_paths[name]].filter(
-					(path) => !path_matches(normalized_url, path) || name in initial_decoded_cookies
-				)
-			);
-		}
-		// Add all new cookies we might not have seen before
-		for (const name in initial_decoded_cookies) {
-			cookie_paths[name] = cookie_paths[name] ?? new Set();
-			if (![...cookie_paths[name]].some((path) => path_matches(normalized_url, path))) {
-				cookie_paths[name].add(default_path);
-			}
-		}
-	}
-
 	/** @type {Record<string, import('./page/types').Cookie>} */
 	const new_cookies = {};
 
@@ -82,20 +62,24 @@ export function get_cookies(request, url, trailing_slash) {
 			const req_cookies = parse(header, { decode: decoder });
 			const cookie = req_cookies[name]; // the decoded string or undefined
 
-			if (!__SVELTEKIT_DEV__ || cookie) {
-				return cookie;
+			// in development, if the cookie was set during this session with `cookies.set`,
+			// but at a different path, warn the user. (ignore cookies from request headers,
+			// since we don't know which path they were set at)
+			if (__SVELTEKIT_DEV__ && !cookie) {
+				const paths = Array.from(cookie_paths[name] ?? []).filter((path) => {
+					// we only care about paths that are _more_ specific than the current path
+					return path_matches(path, url.pathname) && path !== url.pathname;
+				});
+
+				if (paths.length > 0) {
+					console.warn(
+						// prettier-ignore
+						`'${name}' cookie does not exist for ${url.pathname}, but was previously set at ${conjoin([...paths])}. Did you mean to set its 'path' to '/' instead?`
+					);
+				}
 			}
 
-			const paths = new Set([...(cookie_paths[name] ?? [])]);
-			if (c) {
-				paths.add(c.options.path ?? default_path);
-			}
-			if (paths.size > 0) {
-				console.warn(
-					// prettier-ignore
-					`Cookie with name '${name}' was not found at path '${url.pathname}', but a cookie with that name exists at these paths: '${[...paths].join("', '")}'. Did you mean to set its 'path' to '/' instead?`
-				);
-			}
+			return cookie;
 		},
 
 		/**
@@ -141,18 +125,11 @@ export function get_cookies(request, url, trailing_slash) {
 					throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
 				}
 
-				cookie_paths[name] = cookie_paths[name] ?? new Set();
+				cookie_paths[name] ??= new Set();
+
 				if (!value) {
-					if (!cookie_paths[name].has(path) && cookie_paths[name].size > 0) {
-						const paths = `'${Array.from(cookie_paths[name]).join("', '")}'`;
-						console.warn(
-							`Trying to delete cookie '${name}' at path '${path}', but a cookie with that name only exists at these paths: ${paths}.`
-						);
-					}
 					cookie_paths[name].delete(path);
 				} else {
-					// We could also emit a warning here if the cookie already exists at a different path,
-					// but that's more likely a false positive because it's valid to set the same name at different paths
 					cookie_paths[name].add(path);
 				}
 			}
@@ -255,3 +232,11 @@ export function add_cookies_to_headers(headers, cookies) {
 		headers.append('set-cookie', serialize(name, value, options));
 	}
 }
+
+/**
+ * @param {string[]} array
+ */
+function conjoin(array) {
+	if (array.length <= 2) return array.join(' and ');
+	return `${array.slice(0, -1).join(', ')} and ${array.at(-1)}`;
+}
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index da2659fc4..22be19737 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -268,7 +268,7 @@ export async function render_response({
 		}
 	}
 
-	const global = `__sveltekit_${options.version_hash}`;
+	const global = __SVELTEKIT_DEV__ ? `__sveltekit_dev` : `__sveltekit_${options.version_hash}`;
 
 	const { data, chunks } = get_data(
 		event,
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index 30461f8c5..a5a9fdb93 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -157,7 +157,7 @@ export function exec(match, params, matchers) {
 			// and the next value is defined, otherwise the buffer will cause us to skip values
 			const next_param = params[i + 1];
 			const next_value = values[i + 1];
-			if (next_param && !next_param.rest && next_value) {
+			if (next_param && !next_param.rest && next_param.optional && next_value) {
 				buffered = 0;
 			}
 			continue;
diff --git a/packages/kit/src/utils/routing.spec.js b/packages/kit/src/utils/routing.spec.js
index 93a886b20..4ba8737fe 100644
--- a/packages/kit/src/utils/routing.spec.js
+++ b/packages/kit/src/utils/routing.spec.js
@@ -197,6 +197,11 @@ const exec_tests = [
 		route: '/[[slug1=doesntmatch]]/[[slug2=matches]]/constant/[[slug3=matches]]',
 		path: '/b/constant/c',
 		expected: { slug2: 'b', slug3: 'c' }
+	},
+	{
+		route: '/[[slug1=doesntmatch]]/[slug2=matches]/[slug3]',
+		path: '/a/b/c',
+		expected: undefined
 	}
 ];
 
diff --git a/packages/kit/src/utils/streaming.js b/packages/kit/src/utils/streaming.js
index f5fcce837..1015231f4 100644
--- a/packages/kit/src/utils/streaming.js
+++ b/packages/kit/src/utils/streaming.js
@@ -23,22 +23,29 @@ function defer() {
  * }}
  */
 export function create_async_iterator() {
-	let deferred = defer();
+	let deferred = [defer()];
 
 	return {
 		iterator: {
 			[Symbol.asyncIterator]() {
 				return {
-					next: () => deferred.promise
+					next: async () => {
+						const next = await deferred[0].promise;
+						if (!next.done) deferred.shift();
+						return next;
+					}
 				};
 			}
 		},
 		push: (value) => {
-			deferred.fulfil({ value, done: false });
-			deferred = defer();
+			deferred[deferred.length - 1].fulfil({
+				value,
+				done: false
+			});
+			deferred.push(defer());
 		},
 		done: () => {
-			deferred.fulfil({ done: true });
+			deferred[deferred.length - 1].fulfil({ done: true });
 		}
 	};
 }
diff --git a/packages/kit/src/utils/streaming.spec.js b/packages/kit/src/utils/streaming.spec.js
new file mode 100644
index 000000000..ccee5a99f
--- /dev/null
+++ b/packages/kit/src/utils/streaming.spec.js
@@ -0,0 +1,20 @@
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+import { create_async_iterator } from './streaming.js';
+
+test(`works with fast consecutive promise resolutions`, async () => {
+	const iterator = create_async_iterator();
+
+	Promise.resolve(1).then((n) => iterator.push(n));
+	Promise.resolve(2).then((n) => iterator.push(n));
+	Promise.resolve().then(() => iterator.done());
+
+	const actual = [];
+	for await (const value of iterator.iterator) {
+		actual.push(value);
+	}
+
+	assert.equal(actual, [1, 2]);
+});
+
+test.run();
diff --git a/packages/kit/src/utils/url.spec.js b/packages/kit/src/utils/url.spec.js
index 383c30ea2..906a48046 100644
--- a/packages/kit/src/utils/url.spec.js
+++ b/packages/kit/src/utils/url.spec.js
@@ -54,6 +54,10 @@ describe('resolve', (test) => {
 	test('resolves a fragment link', () => {
 		assert.equal(resolve('/a/b/c', '#foo'), '/a/b/c#foo');
 	});
+
+	test('resolves data: urls', () => {
+		assert.equal(resolve('/a/b/c', 'data:text/plain,hello'), 'data:text/plain,hello');
+	});
 });
 
 describe('normalize_path', (test) => {
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 58027c9c8..a5a0d2637 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -458,6 +458,8 @@ export interface KitConfig {
 		 *
 		 * If `true`, `base` and `assets` imported from `$app/paths` will be replaced with relative asset paths during server-side rendering, resulting in portable HTML.
 		 * If `false`, `%sveltekit.assets%` and references to build artifacts will always be root-relative paths, unless `paths.assets` is an external URL
+		 *
+		 * If your app uses a `<base>` element, you should set this to `false`, otherwise asset URLs will incorrectly be resolved against the `<base>` URL rather than the current page.
 		 * @default undefined
 		 */
 		relative?: boolean | undefined;
@@ -572,7 +574,22 @@ export interface KitConfig {
 	 */
 	version?: {
 		/**
-		 * The current app version string. If specified, this must be deterministic (e.g. a commit ref rather than `Math.random()` or `Date.now().toString()`), otherwise defaults to a timestamp of the build
+		 * The current app version string. If specified, this must be deterministic (e.g. a commit ref rather than `Math.random()` or `Date.now().toString()`), otherwise defaults to a timestamp of the build.
+		 *
+		 * For example, to use the current commit hash, you could do use `git rev-parse HEAD`:
+		 *
+		 * ```js
+		 * /// file: svelte.config.js
+		 * import * as child_process from 'node:child_process';
+		 *
+		 * export default {
+		 *   kit: {
+		 *     version: {
+		 *       name: child_process.execSync('git rev-parse HEAD').toString().trim()
+		 *     }
+		 *   }
+		 * };
+		 * ```
 		 */
 		name?: string;
 		/**
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js
index 604de5301..772321557 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -8,7 +8,7 @@ import 'prismjs/components/prism-typescript.js';
 import 'prism-svelte';
 import { escape, extract_frontmatter, transform } from './markdown.js';
 import { modules } from './type-info.js';
-import { render, replace_placeholders } from './render.js';
+import { replace_placeholders } from './render.js';
 import { parse_route_id } from '../../../../../../packages/kit/src/utils/routing.js';
 import ts from 'typescript';
 import MagicString from 'magic-string';
@@ -164,6 +164,10 @@ export async function read_file(file) {
 						injected.push('// @errors: 7006 7031');
 					}
 
+					if (file.endsWith('10-configuration.md')) {
+						injected.push('// @errors: 2307');
+					}
+
 					// another special case
 					if (source.includes('$lib/types')) {
 						injected.push(`declare module '$lib/types' { export interface User {} }`);
@@ -580,9 +584,11 @@ function convert_to_ts(js_code, indent = '', offset = '') {
 				imports.set(from, new Set([name]));
 			}
 			if (generics !== undefined) {
-				return [name, generics
-					.replaceAll("*", "") // get rid of JSDoc asterisks
-					.replace("  }>", "}>") // unindent closing brace
+				return [
+					name,
+					generics
+						.replaceAll('*', '') // get rid of JSDoc asterisks
+						.replace('  }>', '}>') // unindent closing brace
 				];
 			}
 		}
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/render.js b/sites/kit.svelte.dev/src/lib/docs/server/render.js
index e295909e2..6762c17ce 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/render.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/render.js
@@ -95,29 +95,6 @@ export function replace_placeholders(content) {
 		});
 }
 
-/** @param {'types' | 'exports'} kind */
-export function render(kind) {
-	return modules
-		.map((module) => {
-			// special case — we want to include $lib etc in the modules page
-			const is_exempt = kind === 'exports' && module.exempt;
-			const skip = module[kind].length === 0 && !is_exempt;
-
-			if (skip) return '';
-
-			return `## ${module.name}\n\n${module.comment}\n\n${module[kind]
-				.map((type) => {
-					const markdown =
-						`<div class="ts-block">${fence(type.snippet)}` +
-						type.children.map(stringify).join('\n\n') +
-						`</div>`;
-					return `### ${type.name}\n\n${type.comment}\n\n${markdown}`;
-				})
-				.join('\n\n')}`;
-		})
-		.join('\n\n');
-}
-
 /**
  * @param {string} code
  * @param {string} lang


Test patch:
diff --git a/packages/kit/test/prerendering/paths-base/src/app.html b/packages/kit/test/prerendering/paths-base/src/app.html
index cee411bbe..19fc5c4ab 100644
--- a/packages/kit/test/prerendering/paths-base/src/app.html
+++ b/packages/kit/test/prerendering/paths-base/src/app.html
@@ -3,7 +3,8 @@
 	<head>
 		<meta charset="utf-8" />
 		<meta name="viewport" content="width=device-width" />
-		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
+		<base href="/path-base/" />
+		<link rel="icon" href="favicon.png" />
 		%sveltekit.head%
 	</head>
 	<body>
diff --git a/packages/kit/test/prerendering/paths-base/src/routes/+page.svelte b/packages/kit/test/prerendering/paths-base/src/routes/+page.svelte
index 0e0a3e0d6..715969239 100644
--- a/packages/kit/test/prerendering/paths-base/src/routes/+page.svelte
+++ b/packages/kit/test/prerendering/paths-base/src/routes/+page.svelte
@@ -1,8 +1,4 @@
-<script>
-	import { base } from '$app/paths'
-</script>
-
 <div>
 	<h1>Main Page</h1>
-	<a href="{base}/nested">Nested Link</a>
+	<a href="nested">Nested Link</a>
 </div>
diff --git a/packages/kit/test/prerendering/paths-base/src/routes/a/b/c/+page.svelte b/packages/kit/test/prerendering/paths-base/src/routes/a/b/c/+page.svelte
new file mode 100644
index 000000000..e2608ff10
--- /dev/null
+++ b/packages/kit/test/prerendering/paths-base/src/routes/a/b/c/+page.svelte
@@ -0,0 +1,1 @@
+<a href="a/b/d">a/b/d</a>
diff --git a/packages/kit/test/prerendering/paths-base/src/routes/a/b/d/+page.svelte b/packages/kit/test/prerendering/paths-base/src/routes/a/b/d/+page.svelte
new file mode 100644
index 000000000..46a8aaeaf
--- /dev/null
+++ b/packages/kit/test/prerendering/paths-base/src/routes/a/b/d/+page.svelte
@@ -0,0 +1,1 @@
+<a href="a/b/c">a/b/c</a>
diff --git a/packages/kit/test/prerendering/paths-base/svelte.config.js b/packages/kit/test/prerendering/paths-base/svelte.config.js
index 083606280..27e3e4661 100644
--- a/packages/kit/test/prerendering/paths-base/svelte.config.js
+++ b/packages/kit/test/prerendering/paths-base/svelte.config.js
@@ -6,7 +6,8 @@ const config = {
 		adapter: adapter(),
 
 		paths: {
-			base: '/path-base'
+			base: '/path-base',
+			relative: false
 		}
 	}
 };
diff --git a/packages/kit/test/prerendering/paths-base/test/test.js b/packages/kit/test/prerendering/paths-base/test/test.js
index 92c8dba2d..b12f6ee9a 100644
--- a/packages/kit/test/prerendering/paths-base/test/test.js
+++ b/packages/kit/test/prerendering/paths-base/test/test.js
@@ -10,7 +10,7 @@ const read = (file) => fs.readFileSync(`${build}/${file}`, 'utf-8');
 
 test('prerenders /path-base', () => {
 	const content = read('index.html');
-	assert.ok(content.includes('/path-base/favicon.png') && content.includes('/path-base/nested'));
+	assert.ok(content.includes('favicon.png') && content.includes('nested'));
 });
 
 test('prerenders /path-base/redirect', () => {