Repository: sveltejs/kit
Pull request #9145 (2023-02-21 07:06:37)

feat: Validate Vercel cron paths
Vercel Cron Jobs are launching. Thanks to their API, we don't actually have to make any changes to support them.  However, as a courtesy to our users, we _can_ make sure that a SvelteKit app exports a `GET` API handler that matches the path for each cron job. We can't do more than warn here, as users could handle these requests in `handle`, but I think the normal usecase would be to define a `+server.ts` endpoint.

- [ ] Is there a way to test this?
- [ ] Do we want to enable suppression of these warnings via a config option?

### 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.
- [ ] 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/exit-pre-manually.js b/.changeset/exit-pre-manually.js
deleted file mode 100644
index 59a962214..000000000
--- a/.changeset/exit-pre-manually.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import glob from 'tiny-glob/sync.js';
-import { unlinkSync, readFileSync, writeFileSync } from 'fs';
-import { dirname } from 'path';
-import { fileURLToPath } from 'url';
-
-// cleanup changests dir, remove all changesets and pre.json
-const changesets_dir = dirname(fileURLToPath(import.meta.url));
-const changeset_files = glob(`${changesets_dir}/*-*-*.md`);
-changeset_files.forEach((f) => unlinkSync(f));
-unlinkSync(`${changesets_dir}/pre.json`);
-console.log(`removed ${changeset_files.length} changesets`);
-
-// update packages
-// set version
-// add changelog entry
-const packages_dir = fileURLToPath(new URL('../packages', import.meta.url));
-const package_files = glob(`${packages_dir}/*/package.json`);
-for (const pkg_file of package_files) {
-	const pkg = JSON.parse(readFileSync(pkg_file, 'utf-8'));
-	const { version } = pkg;
-	if (version.includes('-next')) {
-		pkg.version = version.replace(/(\d+\.\d+\.\d+)-next.*/, '$1');
-		// remove -next from peerDependencies
-		if (pkg.peerDependencies) {
-			pkg.peerDependencies = Object.fromEntries(
-				Object.entries(pkg.peerDependencies).map(([k, v]) => [
-					k,
-					v.replace(/(\d+\.\d+\.\d+)-next.*/, '$1')
-				])
-			);
-		}
-		writeFileSync(pkg_file, JSON.stringify(pkg, null, '\t') + '\n');
-		const changelog_file = pkg_file.replace(/package\.json$/, 'CHANGELOG.md');
-		let changelog = readFileSync(changelog_file, 'utf-8');
-		changelog = update_changelog(changelog, pkg);
-		writeFileSync(changelog_file, changelog, 'utf-8');
-	} else {
-		throw new Error(`invalid version ${version}, expected -next tag for ${pkg.name}`);
-	}
-	console.log(`updated ${pkg.name}@${version} to ${pkg.version}`);
-}
-const create_svelte_template_dir = fileURLToPath(
-	new URL('../packages/create-svelte/templates', import.meta.url)
-);
-const create_svelte_templates = glob(`${create_svelte_template_dir}/*/package.template.json`);
-for (const create_svelte_template_pkg_file of create_svelte_templates) {
-	const pkg = JSON.parse(readFileSync(create_svelte_template_pkg_file, 'utf-8'));
-	// remove -next from devDependencies
-	if (pkg.devDependencies) {
-		pkg.devDependencies = Object.fromEntries(
-			Object.entries(pkg.devDependencies).map(([k, v]) => [
-				k,
-				v.replace(/(\d+\.\d+\.\d+)-next.*/, '$1')
-			])
-		);
-	}
-	writeFileSync(create_svelte_template_pkg_file, JSON.stringify(pkg, null, '\t') + '\n');
-	console.log(`removed -next from devDependencies in ${create_svelte_template_pkg_file}`);
-}
-
-function update_changelog(changelog, pkg) {
-	const prefix = `# ${pkg.name}\n`;
-	if (!changelog.startsWith(prefix)) {
-		throw new Error(`changelog invalid for ${pkg.name}. must start with ''${prefix}`);
-	}
-	return changelog.replace(prefix, `${prefix}${changelog_entry(pkg.version)}`);
-}
-
-function changelog_entry(version) {
-	return `
-## ${version}
-
-### Major Changes
-
-First major release, see below for the history of changes that lead up to this.
-Starting from now all releases follow semver and changes will be listed as Major/Minor/Patch
-`;
-}
diff --git a/.changeset/tiny-deers-complain.md b/.changeset/tiny-deers-complain.md
new file mode 100644
index 000000000..6da8db783
--- /dev/null
+++ b/.changeset/tiny-deers-complain.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-vercel': minor
+---
+
+feat: validate that Vercel cron paths match an API path
diff --git a/.eslintrc.json b/.eslintrc.json
index 702124958..c8e343bc8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -8,6 +8,7 @@
 	},
 	"plugins": ["unicorn"],
 	"root": true,
+	"ignorePatterns": ["packages/create-svelte/shared/"],
 	"rules": {
 		"unicorn/prefer-node-protocol": "error"
 	}
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
new file mode 100644
index 000000000..c76a4efc0
--- /dev/null
+++ b/.github/workflows/audit.yml
@@ -0,0 +1,26 @@
+name: audit
+
+on:
+  schedule:
+    - cron: "0 5 * * *" # daily 5AM
+
+env:
+  # not needed for audit
+  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
+
+permissions:
+  contents: read # to fetch code (actions/checkout)
+
+jobs:
+  Audit:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: pnpm/action-setup@v2.2.4
+      - uses: actions/setup-node@v3
+        with:
+          node-version: '16.x'
+          cache: pnpm
+      - run: pnpm install --frozen-lockfile
+      # check prod dependencies as these would affect users
+      - run: pnpm audit --prod
diff --git a/documentation/docs/20-core-concepts/30-form-actions.md b/documentation/docs/20-core-concepts/30-form-actions.md
index bfd57beab..8d8d4bb38 100644
--- a/documentation/docs/20-core-concepts/30-form-actions.md
+++ b/documentation/docs/20-core-concepts/30-form-actions.md
@@ -333,7 +333,7 @@ The easiest way to progressively enhance a form is to add the `use:enhance` acti
 
 Without an argument, `use:enhance` will emulate the browser-native behaviour, just without the full-page reloads. It will:
 
-- update the `form` property, `$page.form` and `$page.status` on a successful or invalid response, but only if the action is on the same page you're submitting from. So for example if your form looks like `<form action="/somewhere/else" ..>`, `form` and `$page` will _not_ be updated. This is because in the native form submission case you would be redirected to the page the action is on.
+- update the `form` property, `$page.form` and `$page.status` on a successful or invalid response, but only if the action is on the same page you're submitting from. So for example if your form looks like `<form action="/somewhere/else" ..>`, `form` and `$page` will _not_ be updated. This is because in the native form submission case you would be redirected to the page the action is on. If you want to have them updated either way, use [`applyAction`](#progressive-enhancement-applyaction)
 - reset the `<form>` element and invalidate all data using `invalidateAll` on a successful response
 - call `goto` on a redirect response
 - render the nearest `+error` boundary if an error occurs
@@ -473,4 +473,4 @@ Some forms don't need to `POST` data to the server — search inputs, for exampl
 </form>
 ```
 
-As with `<a>` elements, you can set the [`data-sveltekit-reload`](link-options#data-sveltekit-reload) and [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) attributes on the `<form>` to control the router's behaviour.
+Submitting this form will navigate to `/search?q=...` and invoke your load function but will not invoke an action. As with `<a>` elements, you can set the [`data-sveltekit-reload`](link-options#data-sveltekit-reload) and [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) attributes on the `<form>` to control the router's behaviour.
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 f7048bedd..f8af999a1 100644
--- a/documentation/docs/25-build-and-deploy/40-adapter-node.md
+++ b/documentation/docs/25-build-and-deploy/40-adapter-node.md
@@ -141,21 +141,21 @@ Enables precompressing using gzip and brotli for assets and prerendered pages. I
 
 If you need to change the name of the environment variables used to configure the deployment (for example, to deconflict with environment variables you don't control), you can specify a prefix:
 
-### polyfill
-
-Controls whether your build will load polyfills for missing modules. It defaults to `true`, and should only be disabled when using Node 18.11 or greater.
-
 ```js
 envPrefix: 'MY_CUSTOM_';
 ```
 
-```
+```sh
 MY_CUSTOM_HOST=127.0.0.1 \
 MY_CUSTOM_PORT=4000 \
 MY_CUSTOM_ORIGIN=https://my.site \
 node build
 ```
 
+### polyfill
+
+Controls whether your build will load polyfills for missing modules. It defaults to `true`, and should only be disabled when using Node 18.11 or greater.
+
 ## Custom server
 
 The adapter creates two files in your build directory — `index.js` and `handler.js`. Running `index.js` — e.g. `node build`, if you use the default build directory — will start a server on the configured port.
diff --git a/documentation/docs/30-advanced/70-packaging.md b/documentation/docs/30-advanced/70-packaging.md
index d69f9b4ff..931ae5620 100644
--- a/documentation/docs/30-advanced/70-packaging.md
+++ b/documentation/docs/30-advanced/70-packaging.md
@@ -152,7 +152,7 @@ The second option is to (ab)use the `typesVersions` feature from TypeScript to w
 }
 ```
 
-`>4.0` tells TypeScript to check the inner map if the used TypeScript version is greater than 4 (which should in practice always be true). The inner map tells TypeScript that the typings for `your-library/foo` are found within `./dist/foo.d.ts`, which essentially replicates the `exports` condition. You also have `*` as a wildcard at your disposal to make many type definitions at once available without repeating yourself.
+`>4.0` tells TypeScript to check the inner map if the used TypeScript version is greater than 4 (which should in practice always be true). The inner map tells TypeScript that the typings for `your-library/foo` are found within `./dist/foo.d.ts`, which essentially replicates the `exports` condition. You also have `*` as a wildcard at your disposal to make many type definitions at once available without repeating yourself. Note that if you opt into `typesVersions` you have to declare all type imports through it, including the root import (which is defined as `"index": [..]`).
 
 You can read more about that feature [here](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions).
 
@@ -195,7 +195,7 @@ You should think carefully about whether or not the changes you make to your pac
 
 - `-w`/`--watch` — watch files in `src/lib` for changes and rebuild the package
 - `-i`/`--input` — the input directory which contains all the files of the package. Defaults to `src/lib`
-- `-o`/`--o` — the output directory where the processed files are written to. You `package.json`'s `exports` should point to files inside there, and the `files` array should include that folder. Defaults to `dist`
+- `-o`/`--o` — the output directory where the processed files are written to. Your `package.json`'s `exports` should point to files inside there, and the `files` array should include that folder. Defaults to `dist`
 - `-t`/`--types` — whether or not to create type definitions (`d.ts` files). We strongly recommend doing this as it fosters ecosystem library quality. Defaults to `true`
 
 ## Publishing
diff --git a/package.json b/package.json
index ea2a8af06..a1773b761 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
 		"test:cross-platform:build": "pnpm run --dir packages/kit test:cross-platform:build",
 		"test:vite-ecosystem-ci": "pnpm test --dir packages/kit",
 		"check": "pnpm -r check",
-		"lint": "pnpm -r lint",
+		"lint": "pnpm -r lint && eslint 'packages/**/*.js'",
 		"format": "pnpm -r format",
 		"precommit": "pnpm format && pnpm lint",
 		"release": "changeset publish",
@@ -31,6 +31,7 @@
 		"@rollup/plugin-json": "^6.0.0",
 		"@rollup/plugin-node-resolve": "^15.0.1",
 		"@svitejs/changesets-changelog-github-compact": "^1.1.0",
+		"@typescript-eslint/eslint-plugin": "^5.53.0",
 		"eslint": "^8.33.0",
 		"eslint-plugin-unicorn": "^45.0.2",
 		"playwright": "^1.29.2",
@@ -40,7 +41,7 @@
 		"tiny-glob": "^0.2.9",
 		"typescript": "^4.9.4"
 	},
-	"packageManager": "pnpm@7.27.1",
+	"packageManager": "pnpm@7.28.0",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/adapter-auto/index.js b/packages/adapter-auto/index.js
index 9e73f5092..14115c45b 100644
--- a/packages/adapter-auto/index.js
+++ b/packages/adapter-auto/index.js
@@ -1,9 +1,9 @@
-import { execSync } from 'child_process';
-import { pathToFileURL } from 'url';
+import { execSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
 import { resolve } from 'import-meta-resolve';
 import { adapters } from './adapters.js';
-import { dirname, join } from 'path';
-import { existsSync } from 'fs';
+import { dirname, join } from 'node:path';
+import { existsSync } from 'node:fs';
 
 /** @type {Record<string, (name: string, version: string) => string>} */
 const commands = {
diff --git a/packages/adapter-cloudflare-workers/index.js b/packages/adapter-cloudflare-workers/index.js
index 2a38c5a67..882881baf 100644
--- a/packages/adapter-cloudflare-workers/index.js
+++ b/packages/adapter-cloudflare-workers/index.js
@@ -1,9 +1,9 @@
-import { existsSync, readFileSync, writeFileSync } from 'fs';
-import { posix, dirname } from 'path';
-import { execSync } from 'child_process';
+import { existsSync, readFileSync, writeFileSync } from 'node:fs';
+import { posix, dirname } from 'node:path';
+import { execSync } from 'node:child_process';
 import esbuild from 'esbuild';
 import toml from '@iarna/toml';
-import { fileURLToPath } from 'url';
+import { fileURLToPath } from 'node:url';
 
 /**
  * @typedef {{
diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js
index e4dd739fc..c151afc2c 100644
--- a/packages/adapter-static/index.js
+++ b/packages/adapter-static/index.js
@@ -1,4 +1,4 @@
-import path from 'path';
+import path from 'node:path';
 import { platforms } from './platforms.js';
 
 /** @type {import('.').default} */
diff --git a/packages/adapter-static/platforms.js b/packages/adapter-static/platforms.js
index 3ecf37cb3..189865b10 100644
--- a/packages/adapter-static/platforms.js
+++ b/packages/adapter-static/platforms.js
@@ -1,4 +1,4 @@
-import fs from 'fs';
+import fs from 'node:fs';
 
 /**
  * @typedef {{
diff --git a/packages/adapter-vercel/CHANGELOG.md b/packages/adapter-vercel/CHANGELOG.md
index 459364d7b..2ab0bdb25 100644
--- a/packages/adapter-vercel/CHANGELOG.md
+++ b/packages/adapter-vercel/CHANGELOG.md
@@ -1,5 +1,19 @@
 # @sveltejs/adapter-vercel
 
+## 2.1.1
+
+### Patch Changes
+
+- fix: move catch-all-regex to the end ([#9185](https://github.com/sveltejs/kit/pull/9185))
+
+- fix: temporarily disable streaming when ISR is used ([#9197](https://github.com/sveltejs/kit/pull/9197))
+
+## 2.1.0
+
+### Minor Changes
+
+- feat: enable experimental response streaming ([#9144](https://github.com/sveltejs/kit/pull/9144))
+
 ## 2.0.4
 
 ### Patch Changes
diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js
index e9b4909a5..d66fc6680 100644
--- a/packages/adapter-vercel/index.js
+++ b/packages/adapter-vercel/index.js
@@ -39,6 +39,12 @@ const plugin = function (defaults = {}) {
 			builder.rimraf(dir);
 			builder.rimraf(tmp);
 
+			if (fs.existsSync('vercel.json')) {
+				const vercel_file = fs.readFileSync('vercel.json', 'utf-8');
+				const vercel_config = JSON.parse(vercel_file);
+				validate_vercel_json(builder, vercel_config);
+			}
+
 			const files = fileURLToPath(new URL('./files', import.meta.url).href);
 
 			const dirs = {
@@ -216,11 +222,6 @@ const plugin = function (defaults = {}) {
 					/** @type {import('@sveltejs/kit').RouteDefinition<any>[]} */ (group.routes)
 				);
 
-				if (singular) {
-					// Special case: One function for all routes
-					static_config.routes.push({ src: '/.*', dest: `/${name}` });
-				}
-
 				for (const route of group.routes) {
 					functions.set(route.pattern.toString(), name);
 				}
@@ -286,6 +287,12 @@ const plugin = function (defaults = {}) {
 				}
 			}
 
+			if (singular) {
+				// Common case: One function for all routes
+				// Needs to happen after ISR or else regex swallows all other matches
+				static_config.routes.push({ src: '/.*', dest: `/fn` });
+			}
+
 			builder.log.minor('Copying assets...');
 
 			builder.writeClient(dirs.static);
@@ -487,7 +494,8 @@ async function create_function_bundle(builder, entry, dir, config) {
 				memory: config.memory,
 				maxDuration: config.maxDuration,
 				handler: path.relative(base + ancestor, entry),
-				launcherType: 'Nodejs'
+				launcherType: 'Nodejs',
+				experimentalResponseStreaming: !config.isr
 			},
 			null,
 			'\t'
@@ -497,4 +505,57 @@ async function create_function_bundle(builder, entry, dir, config) {
 	write(`${dir}/package.json`, JSON.stringify({ type: 'module' }));
 }
 
+/**
+ *
+ * @param {import('@sveltejs/kit').Builder} builder
+ * @param {any} vercel_config
+ */
+function validate_vercel_json(builder, vercel_config) {
+	if (builder.routes.length > 0 && !builder.routes[0].api) {
+		// bail — we're on an older SvelteKit version that doesn't
+		// populate `route.api.methods`, so we can't check
+		// to see if cron paths are valid
+		return;
+	}
+
+	const crons = /** @type {Array<unknown>} */ (
+		Array.isArray(vercel_config?.crons) ? vercel_config.crons : []
+	);
+
+	/** For a route to be considered 'valid', it must be an API route with a GET handler */
+	const valid_routes = builder.routes.filter((route) => route.api.methods.includes('GET'));
+
+	/** @type {Array<string>} */
+	const unmatched_paths = [];
+
+	for (const cron of crons) {
+		if (typeof cron !== 'object' || cron === null || !('path' in cron)) {
+			continue;
+		}
+
+		const { path } = cron;
+		if (typeof path !== 'string') {
+			continue;
+		}
+
+		for (const route of valid_routes) {
+			if (route.pattern.test(path)) {
+				continue;
+			}
+		}
+
+		unmatched_paths.push(path);
+	}
+
+	builder.log.warn(
+		`\nvercel.json defines cron tasks that use paths that do not correspond to an API route with a GET handler (ignore this if the request is handled in your \`handle\` hook):`
+	);
+
+	for (const path of unmatched_paths) {
+		console.log(`    - ${path}`);
+	}
+
+	console.log('');
+}
+
 export default plugin;
diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json
index c3d45be15..7a568977c 100644
--- a/packages/adapter-vercel/package.json
+++ b/packages/adapter-vercel/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/adapter-vercel",
-	"version": "2.0.4",
+	"version": "2.1.1",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md
index d7177b8ca..8fae3bba5 100644
--- a/packages/create-svelte/CHANGELOG.md
+++ b/packages/create-svelte/CHANGELOG.md
@@ -1,5 +1,23 @@
 # create-svelte
 
+## 3.1.0
+
+### Minor Changes
+
+- feat: use `@clack/prompts` ([#9219](https://github.com/sveltejs/kit/pull/9219))
+
+## 3.0.4
+
+### Patch Changes
+
+- docs: inform about adapter-auto not supporting all environments ([#9196](https://github.com/sveltejs/kit/pull/9196))
+
+## 3.0.3
+
+### Patch Changes
+
+- chore: enhance library skeleton readme with some library instructions ([#9128](https://github.com/sveltejs/kit/pull/9128))
+
 ## 3.0.2
 
 ### Patch Changes
diff --git a/packages/create-svelte/bin.js b/packages/create-svelte/bin.js
index 994cb2d3b..f9e109b5e 100755
--- a/packages/create-svelte/bin.js
+++ b/packages/create-svelte/bin.js
@@ -1,187 +1,168 @@
 #!/usr/bin/env node
-import fs from 'fs';
-import path from 'path';
-import { bold, cyan, gray, green, yellow } from 'kleur/colors';
-import prompts from 'prompts';
+import fs from 'node:fs';
+import path from 'node:path';
+import * as p from '@clack/prompts';
+import { bold, cyan, grey, yellow } from 'kleur/colors';
 import { create } from './index.js';
 import { dist } from './utils.js';
 
-// prettier-ignore
-const disclaimer = `
-${bold(cyan('Welcome to SvelteKit!'))}
-`;
-
 const { version } = JSON.parse(fs.readFileSync(new URL('package.json', import.meta.url), 'utf-8'));
+let cwd = process.argv[2] || '.';
 
-async function main() {
-	console.log(gray(`\ncreate-svelte version ${version}`));
-	console.log(disclaimer);
+console.log(`
+${grey(`create-svelte version ${version}`)}
+`);
 
-	let cwd = process.argv[2] || '.';
+p.intro('Welcome to SvelteKit!');
 
-	if (cwd === '.') {
-		const opts = await prompts([
-			{
-				type: 'text',
-				name: 'dir',
-				message: 'Where should we create your project?\n  (leave blank to use current directory)'
-			}
-		]);
+if (cwd === '.') {
+	const dir = await p.text({
+		message: 'Where should we create your project?',
+		placeholder: '  (hit Enter to use current directory)'
+	});
 
-		if (opts.dir) {
-			cwd = opts.dir;
-		}
-	}
+	if (p.isCancel(dir)) process.exit(1);
 
-	if (fs.existsSync(cwd)) {
-		if (fs.readdirSync(cwd).length > 0) {
-			const response = await prompts({
-				type: 'confirm',
-				name: 'value',
-				message: 'Directory not empty. Continue?',
-				initial: false
-			});
-
-			if (!response.value) {
-				process.exit(1);
-			}
-		}
+	if (dir) {
+		cwd = /** @type {string} */ (dir);
 	}
+}
 
-	const options = /** @type {import('./types/internal').Options} */ (
-		await prompts(
-			[
-				{
-					type: 'select',
-					name: 'template',
-					message: 'Which Svelte app template?',
-					choices: fs.readdirSync(dist('templates')).map((dir) => {
-						const meta_file = dist(`templates/${dir}/meta.json`);
-						const { title, description } = JSON.parse(fs.readFileSync(meta_file, 'utf8'));
-
-						return {
-							title,
-							description,
-							value: dir
-						};
-					})
-				},
-				{
-					type: 'select',
-					name: 'types',
-					message: 'Add type checking with TypeScript?',
-					initial: false,
-					choices: [
-						{
-							title: 'Yes, using JavaScript with JSDoc comments',
-							value: 'checkjs'
-						},
-						{
-							title: 'Yes, using TypeScript syntax',
-							value: 'typescript'
-						},
-						{ title: 'No', value: null }
-					]
-				},
-				{
-					type: 'toggle',
-					name: 'eslint',
-					message: 'Add ESLint for code linting?',
-					initial: false,
-					active: 'Yes',
-					inactive: 'No'
-				},
-				{
-					type: 'toggle',
-					name: 'prettier',
-					message: 'Add Prettier for code formatting?',
-					initial: false,
-					active: 'Yes',
-					inactive: 'No'
-				},
-				{
-					type: 'toggle',
-					name: 'playwright',
-					message: 'Add Playwright for browser testing?',
-					initial: false,
-					active: 'Yes',
-					inactive: 'No'
-				},
-				{
-					type: 'toggle',
-					name: 'vitest',
-					message: 'Add Vitest for unit testing?',
-					initial: false,
-					active: 'Yes',
-					inactive: 'No'
-				}
-			],
-			{
-				onCancel: () => {
-					process.exit(1);
-				}
-			}
-		)
-	);
-
-	options.name = path.basename(path.resolve(cwd));
-
-	await create(cwd, options);
-
-	console.log(bold(green('\nYour project is ready!')));
-
-	if (options.types === 'typescript') {
-		console.log(bold('✔ Typescript'));
-		console.log('  Inside Svelte components, use <script lang="ts">');
-	} else if (options.types === 'checkjs') {
-		console.log(bold('✔ Type-checked JavaScript'));
-		console.log('  https://www.typescriptlang.org/tsconfig#checkJs');
-	} else if (options.template === 'skeletonlib') {
-		const warning = yellow('▲');
-		console.log(
-			`${warning} You chose to not add type checking, but TypeScript will still be installed in order to generate type definitions when building the library`
-		);
-	}
+if (fs.existsSync(cwd)) {
+	if (fs.readdirSync(cwd).length > 0) {
+		const force = await p.confirm({
+			message: 'Directory not empty. Continue?',
+			initialValue: false
+		});
 
-	if (options.eslint) {
-		console.log(bold('✔ ESLint'));
-		console.log(cyan('  https://github.com/sveltejs/eslint-plugin-svelte3'));
+		// bail if `force` is `false` or the user cancelled with Ctrl-C
+		if (force !== true) {
+			process.exit(1);
+		}
 	}
+}
 
-	if (options.prettier) {
-		console.log(bold('✔ Prettier'));
-		console.log(cyan('  https://prettier.io/docs/en/options.html'));
-		console.log(cyan('  https://github.com/sveltejs/prettier-plugin-svelte#options'));
-	}
+const options = await p.group(
+	{
+		template: () =>
+			p.select({
+				message: 'Which Svelte app template?',
+				options: fs.readdirSync(dist('templates')).map((dir) => {
+					const meta_file = dist(`templates/${dir}/meta.json`);
+					const { title, description } = JSON.parse(fs.readFileSync(meta_file, 'utf8'));
+
+					return {
+						label: title,
+						hint: description,
+						value: dir
+					};
+				})
+			}),
+
+		types: () =>
+			p.select({
+				message: 'Add type checking with TypeScript?',
+				options: [
+					{
+						label: 'Yes, using JavaScript with JSDoc comments',
+						value: 'checkjs'
+					},
+					{
+						label: 'Yes, using TypeScript syntax',
+						value: 'typescript'
+					},
+					{ label: 'No', value: null }
+				]
+			}),
+
+		features: () =>
+			p.multiselect({
+				message: 'Select additional options',
+				required: false,
+				options: [
+					{
+						value: 'eslint',
+						label: 'Add ESLint for code linting'
+					},
+					{
+						value: 'prettier',
+						label: 'Add Prettier for code formatting'
+					},
+					{
+						value: 'playwright',
+						label: 'Add Playwright for browser testing'
+					},
+					{
+						value: 'vitest',
+						label: 'Add Vitest for unit testing'
+					}
+				]
+			})
+	},
+	{ onCancel: () => process.exit(1) }
+);
+
+await create(cwd, {
+	name: path.basename(path.resolve(cwd)),
+	template: /** @type {'default' | 'skeleton' | 'skeletonlib'} */ (options.template),
+	types: options.types,
+	prettier: options.features.includes('prettier'),
+	eslint: options.features.includes('eslint'),
+	playwright: options.features.includes('playwright'),
+	vitest: options.features.includes('vitest')
+});
+
+p.outro(`Your project is ready!`);
+
+if (options.types === 'typescript') {
+	console.log(bold('✔ Typescript'));
+	console.log('  Inside Svelte components, use <script lang="ts">\n');
+} else if (options.types === 'checkjs') {
+	console.log(bold('✔ Type-checked JavaScript'));
+	console.log(cyan('  https://www.typescriptlang.org/tsconfig#checkJs\n'));
+} else if (options.template === 'skeletonlib') {
+	const warning = yellow('▲');
+	console.log(
+		`${warning} You chose to not add type checking, but TypeScript will still be installed in order to generate type definitions when building the library\n`
+	);
+}
 
-	if (options.playwright) {
-		console.log(bold('✔ Playwright'));
-		console.log(cyan('  https://playwright.dev'));
-	}
+if (options.features.includes('eslint')) {
+	console.log(bold('✔ ESLint'));
+	console.log(cyan('  https://github.com/sveltejs/eslint-plugin-svelte3\n'));
+}
 
-	if (options.vitest) {
-		console.log(bold('✔ Vitest'));
-		console.log(cyan('  https://vitest.dev'));
-	}
+if (options.features.includes('prettier')) {
+	console.log(bold('✔ Prettier'));
+	console.log(cyan('  https://prettier.io/docs/en/options.html'));
+	console.log(cyan('  https://github.com/sveltejs/prettier-plugin-svelte#options\n'));
+}
 
-	console.log('\nInstall community-maintained integrations:');
-	console.log(cyan('  https://github.com/svelte-add/svelte-add'));
+if (options.features.includes('playwright')) {
+	console.log(bold('✔ Playwright'));
+	console.log(cyan('  https://playwright.dev\n'));
+}
 
-	console.log('\nNext steps:');
-	let i = 1;
+if (options.features.includes('vitest')) {
+	console.log(bold('✔ Vitest'));
+	console.log(cyan('  https://vitest.dev\n'));
+}
 
-	const relative = path.relative(process.cwd(), cwd);
-	if (relative !== '') {
-		console.log(`  ${i++}: ${bold(cyan(`cd ${relative}`))}`);
-	}
+console.log('Install community-maintained integrations:');
+console.log(cyan('  https://github.com/svelte-add/svelte-add'));
 
-	console.log(`  ${i++}: ${bold(cyan('npm install'))} (or pnpm install, etc)`);
-	// prettier-ignore
-	console.log(`  ${i++}: ${bold(cyan('git init && git add -A && git commit -m "Initial commit"'))} (optional)`);
-	console.log(`  ${i++}: ${bold(cyan('npm run dev -- --open'))}`);
+console.log('\nNext steps:');
+let i = 1;
 
-	console.log(`\nTo close the dev server, hit ${bold(cyan('Ctrl-C'))}`);
-	console.log(`\nStuck? Visit us at ${cyan('https://svelte.dev/chat')}`);
+const relative = path.relative(process.cwd(), cwd);
+if (relative !== '') {
+	console.log(`  ${i++}: ${bold(cyan(`cd ${relative}`))}`);
 }
 
-main();
+console.log(`  ${i++}: ${bold(cyan('npm install'))} (or pnpm install, etc)`);
+// prettier-ignore
+console.log(`  ${i++}: ${bold(cyan('git init && git add -A && git commit -m "Initial commit"'))} (optional)`);
+console.log(`  ${i++}: ${bold(cyan('npm run dev -- --open'))}`);
+
+console.log(`\nTo close the dev server, hit ${bold(cyan('Ctrl-C'))}`);
+console.log(`\nStuck? Visit us at ${cyan('https://svelte.dev/chat')}`);
diff --git a/packages/create-svelte/index.js b/packages/create-svelte/index.js
index 58a215dd5..d6888950c 100755
--- a/packages/create-svelte/index.js
+++ b/packages/create-svelte/index.js
@@ -1,5 +1,5 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
 import { mkdirp, copy, dist } from './utils.js';
 
 /** @type {import('./types/index').create} */
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index 7fea81527..8f70c3160 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "create-svelte",
-	"version": "3.0.2",
+	"version": "3.1.0",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -11,8 +11,8 @@
 	"bin": "./bin.js",
 	"main": "./index.js",
 	"dependencies": {
-		"kleur": "^4.1.5",
-		"prompts": "^2.4.2"
+		"@clack/prompts": "^0.6.0",
+		"kleur": "^4.1.5"
 	},
 	"devDependencies": {
 		"@playwright/test": "^1.28.1",
diff --git a/packages/create-svelte/scripts/build-templates.js b/packages/create-svelte/scripts/build-templates.js
index bc52f4f61..7d7c207c1 100644
--- a/packages/create-svelte/scripts/build-templates.js
+++ b/packages/create-svelte/scripts/build-templates.js
@@ -1,5 +1,5 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
 import parser from 'gitignore-parser';
 import prettier from 'prettier';
 import { transform } from 'sucrase';
diff --git a/packages/create-svelte/scripts/update-template-repo-contents.js b/packages/create-svelte/scripts/update-template-repo-contents.js
index 1eb7a07dc..78ec904d6 100644
--- a/packages/create-svelte/scripts/update-template-repo-contents.js
+++ b/packages/create-svelte/scripts/update-template-repo-contents.js
@@ -1,5 +1,5 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
 import { create } from '../index.js';
 
 const repo = process.argv[2];
diff --git a/packages/create-svelte/shared/+default+checkjs/svelte.config.js b/packages/create-svelte/shared/+default+checkjs/svelte.config.js
index 87f198f55..1cf26a00d 100644
--- a/packages/create-svelte/shared/+default+checkjs/svelte.config.js
+++ b/packages/create-svelte/shared/+default+checkjs/svelte.config.js
@@ -8,6 +8,9 @@ const config = {
 	preprocess: vitePreprocess(),
 
 	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 		adapter: adapter()
 	}
 };
diff --git a/packages/create-svelte/shared/+default+typescript/svelte.config.js b/packages/create-svelte/shared/+default+typescript/svelte.config.js
index 87f198f55..1cf26a00d 100644
--- a/packages/create-svelte/shared/+default+typescript/svelte.config.js
+++ b/packages/create-svelte/shared/+default+typescript/svelte.config.js
@@ -8,6 +8,9 @@ const config = {
 	preprocess: vitePreprocess(),
 
 	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 		adapter: adapter()
 	}
 };
diff --git a/packages/create-svelte/shared/+default-typescript/svelte.config.js b/packages/create-svelte/shared/+default-typescript/svelte.config.js
index 301e785eb..348fa324a 100644
--- a/packages/create-svelte/shared/+default-typescript/svelte.config.js
+++ b/packages/create-svelte/shared/+default-typescript/svelte.config.js
@@ -3,6 +3,9 @@ import adapter from '@sveltejs/adapter-auto';
 /** @type {import('@sveltejs/kit').Config} */
 const config = {
 	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 		adapter: adapter()
 	}
 };
diff --git a/packages/create-svelte/shared/+skeletonlib/README.md b/packages/create-svelte/shared/+skeletonlib/README.md
new file mode 100644
index 000000000..4fee31f6f
--- /dev/null
+++ b/packages/create-svelte/shared/+skeletonlib/README.md
@@ -0,0 +1,58 @@
+# create-svelte
+
+Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
+
+Read more about creating a library [in the docs](https://kit.svelte.dev/docs/packaging).
+
+## Creating a project
+
+If you're seeing this, you've probably already done this step. Congrats!
+
+```bash
+# create a new project in the current directory
+npm create svelte@latest
+
+# create a new project in my-app
+npm create svelte@latest my-app
+```
+
+## Developing
+
+Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
+
+```bash
+npm run dev
+
+# or start the server and open the app in a new browser tab
+npm run dev -- --open
+```
+
+Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
+
+## Building
+
+To build your library:
+
+```bash
+npm run package
+```
+
+To create a production version of your showcase app:
+
+```bash
+npm run build
+```
+
+You can preview the production build with `npm run preview`.
+
+> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
+
+## Publishing
+
+Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
+
+To publish your library to [npm](https://www.npmjs.com):
+
+```bash
+npm publish
+```
diff --git a/packages/create-svelte/shared/+typescript/svelte.config.js b/packages/create-svelte/shared/+typescript/svelte.config.js
index 87f198f55..1cf26a00d 100644
--- a/packages/create-svelte/shared/+typescript/svelte.config.js
+++ b/packages/create-svelte/shared/+typescript/svelte.config.js
@@ -8,6 +8,9 @@ const config = {
 	preprocess: vitePreprocess(),
 
 	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 		adapter: adapter()
 	}
 };
diff --git a/packages/create-svelte/shared/-typescript/svelte.config.js b/packages/create-svelte/shared/-typescript/svelte.config.js
index 301e785eb..348fa324a 100644
--- a/packages/create-svelte/shared/-typescript/svelte.config.js
+++ b/packages/create-svelte/shared/-typescript/svelte.config.js
@@ -3,6 +3,9 @@ import adapter from '@sveltejs/adapter-auto';
 /** @type {import('@sveltejs/kit').Config} */
 const config = {
 	kit: {
+		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
+		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
+		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 		adapter: adapter()
 	}
 };
diff --git a/packages/create-svelte/templates/default/vite.config.js b/packages/create-svelte/templates/default/vite.config.js
index b594e97fa..3dc75a73b 100644
--- a/packages/create-svelte/templates/default/vite.config.js
+++ b/packages/create-svelte/templates/default/vite.config.js
@@ -1,4 +1,4 @@
-import path from 'path';
+import path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 import { defineConfig } from 'vite';
 
diff --git a/packages/create-svelte/utils.js b/packages/create-svelte/utils.js
index 7965eaed2..2f97cddb5 100644
--- a/packages/create-svelte/utils.js
+++ b/packages/create-svelte/utils.js
@@ -1,6 +1,6 @@
-import fs from 'fs';
-import path from 'path';
-import { fileURLToPath } from 'url';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
 
 /** @param {string} dir */
 export function mkdirp(dir) {
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 27c824e03..63b631640 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,33 @@
 # @sveltejs/kit
 
+## 1.8.7
+
+### Patch Changes
+
+- fix: correct not found message when setting `paths.base` ([#9232](https://github.com/sveltejs/kit/pull/9232))
+
+## 1.8.6
+
+### Patch Changes
+
+- fix: bundle SvelteKit when using Vitest ([#9172](https://github.com/sveltejs/kit/pull/9172))
+
+## 1.8.5
+
+### Patch Changes
+
+- fix: allow relative fetch to endpoint outside app from within `handle` ([#9198](https://github.com/sveltejs/kit/pull/9198))
+
+## 1.8.4
+
+### Patch Changes
+
+- fix: include .mjs files in precompression ([#9179](https://github.com/sveltejs/kit/pull/9179))
+
+- fix: revert mjs extension usage by default, make it an option ([#9179](https://github.com/sveltejs/kit/pull/9179))
+
+- chore: dummy changeset to force a release ([#9207](https://github.com/sveltejs/kit/pull/9207))
+
 ## 1.8.3
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index bbf9982b9..d15434037 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.8.3",
+	"version": "1.8.7",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -16,7 +16,7 @@
 		"devalue": "^4.3.0",
 		"esm-env": "^1.0.0",
 		"kleur": "^4.1.5",
-		"magic-string": "^0.29.0",
+		"magic-string": "^0.30.0",
 		"mime": "^3.0.0",
 		"sade": "^1.8.1",
 		"set-cookie-parser": "^2.5.1",
@@ -57,7 +57,7 @@
 		"postinstall.js"
 	],
 	"scripts": {
-		"lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore && eslint src/**/*.js",
+		"lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
 		"check": "tsc",
 		"check:all": "tsc && pnpm -r --filter=\"./**\" check",
 		"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
diff --git a/packages/kit/postinstall.js b/packages/kit/postinstall.js
index 03d5307b4..298ef8a94 100644
--- a/packages/kit/postinstall.js
+++ b/packages/kit/postinstall.js
@@ -1,5 +1,5 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node: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';
diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js
index 499fa7c42..6176475b5 100644
--- a/packages/kit/src/core/adapt/builder.js
+++ b/packages/kit/src/core/adapt/builder.js
@@ -43,14 +43,15 @@ export function create_builder({
 	 * we expose a stable type that adapters can use to group/filter routes
 	 */
 	const routes = route_data.map((route) => {
-		const methods =
-			/** @type {import('types').HttpMethod[]} */
-			(server_metadata.routes.get(route.id)?.methods);
-		const config = server_metadata.routes.get(route.id)?.config;
+		const { config, methods, page, api } = /** @type {import('types').ServerMetadataRoute} */ (
+			server_metadata.routes.get(route.id)
+		);
 
 		/** @type {import('types').RouteDefinition} */
 		const facade = {
 			id: route.id,
+			api,
+			page,
 			segments: get_route_segments(route.id).map((segment) => ({
 				dynamic: segment.includes('['),
 				rest: segment.includes('[...'),
@@ -82,7 +83,7 @@ export function create_builder({
 				return;
 			}
 
-			const files = await glob('**/*.{html,js,json,css,svg,xml,wasm}', {
+			const files = await glob('**/*.{html,js,mjs,json,css,svg,xml,wasm}', {
 				cwd: directory,
 				dot: true,
 				absolute: true,
diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js
index b58ee40be..f6995357a 100644
--- a/packages/kit/src/core/config/index.spec.js
+++ b/packages/kit/src/core/config/index.spec.js
@@ -90,6 +90,7 @@ const get_defaults = (prefix = '') => ({
 		},
 		inlineStyleThreshold: 0,
 		moduleExtensions: ['.js', '.ts'],
+		output: { preloadStrategy: 'modulepreload' },
 		outDir: join(prefix, '.svelte-kit'),
 		serviceWorker: {
 			register: true
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index ecf69c818..3d4d72054 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -138,6 +138,10 @@ const options = object(
 
 			outDir: string('.svelte-kit'),
 
+			output: object({
+				preloadStrategy: list(['modulepreload', 'preload-js', 'preload-mjs'], 'modulepreload')
+			}),
+
 			paths: object({
 				base: validate('', (input, keypath) => {
 					assert_string(input, keypath);
diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js
index 1588c4a4a..b31df2c5f 100644
--- a/packages/kit/src/core/postbuild/analyse.js
+++ b/packages/kit/src/core/postbuild/analyse.js
@@ -62,8 +62,11 @@ async function analyse({ manifest_path, env }) {
 
 	// analyse routes
 	for (const route of manifest._.routes) {
-		/** @type {Set<import('types').HttpMethod>} */
-		const methods = new Set();
+		/** @type {Array<'GET' | 'POST'>} */
+		const page_methods = [];
+
+		/** @type {import('types').HttpMethod[]} */
+		const api_methods = [];
 
 		/** @type {import('types').PrerenderOption | undefined} */
 		let prerender = undefined;
@@ -84,12 +87,12 @@ async function analyse({ manifest_path, env }) {
 				prerender = mod.prerender;
 			}
 
-			if (mod.GET) methods.add('GET');
-			if (mod.POST) methods.add('POST');
-			if (mod.PUT) methods.add('PUT');
-			if (mod.PATCH) methods.add('PATCH');
-			if (mod.DELETE) methods.add('DELETE');
-			if (mod.OPTIONS) methods.add('OPTIONS');
+			if (mod.GET) api_methods.push('GET');
+			if (mod.POST) api_methods.push('POST');
+			if (mod.PUT) api_methods.push('PUT');
+			if (mod.PATCH) api_methods.push('PATCH');
+			if (mod.DELETE) api_methods.push('DELETE');
+			if (mod.OPTIONS) api_methods.push('OPTIONS');
 
 			config = mod.config;
 		}
@@ -112,8 +115,8 @@ async function analyse({ manifest_path, env }) {
 			}
 
 			if (page) {
-				methods.add('GET');
-				if (page.server?.actions) methods.add('POST');
+				page_methods.push('GET');
+				if (page.server?.actions) page_methods.push('POST');
 
 				validate_page_server_exports(page.server, page.server_id);
 				validate_common_exports(page.universal, page.universal_id);
@@ -133,9 +136,15 @@ async function analyse({ manifest_path, env }) {
 		}
 
 		metadata.routes.set(route.id, {
-			prerender,
 			config,
-			methods: Array.from(methods)
+			methods: Array.from(new Set([...page_methods, ...api_methods])),
+			page: {
+				methods: page_methods
+			},
+			api: {
+				methods: api_methods
+			},
+			prerender
 		});
 	}
 
diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js
index 0ccd98c42..4b6cc71df 100644
--- a/packages/kit/src/core/sync/write_server.js
+++ b/packages/kit/src/core/sync/write_server.js
@@ -36,6 +36,7 @@ export const options = {
 	embedded: ${config.kit.embedded},
 	env_public_prefix: '${config.kit.env.publicPrefix}',
 	hooks: null, // added lazily, via \`get_hooks\`
+	preload_strategy: ${s(config.kit.output.preloadStrategy)},
 	root,
 	service_worker: ${has_service_worker},
 	templates: {
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 5517deb6e..ca0693a3a 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -218,6 +218,21 @@ function kit({ svelte_config }) {
 
 			const generated = path.posix.join(kit.outDir, 'generated');
 
+			// This ensures that esm-env is inlined into the server output with the
+			// export conditions resolved correctly through Vite. This prevents adapters
+			// that bundle later on from resolving the export conditions incorrectly
+			// and for example include browser-only code in the server output
+			// because they for example use esbuild.build with `platform: 'browser'`
+			const noExternal = ['esm-env'];
+
+			// Vitest bypasses Vite when loading external modules, so we bundle
+			// when it is detected to keep our virtual modules working.
+			// See https://github.com/sveltejs/kit/pull/9172
+			// and https://vitest.dev/config/#deps-registernodeloader
+			if (process.env.TEST) {
+				noExternal.push('@sveltejs/kit');
+			}
+
 			// dev and preview config can be shared
 			/** @type {import('vite').UserConfig} */
 			const new_config = {
@@ -252,6 +267,9 @@ function kit({ svelte_config }) {
 						'$app',
 						'$env'
 					]
+				},
+				ssr: {
+					noExternal
 				}
 			};
 
@@ -267,19 +285,6 @@ function kit({ svelte_config }) {
 					__SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
 				};
 
-				new_config.ssr = {
-					noExternal: [
-						// TODO document why this is necessary
-						'@sveltejs/kit',
-						// This ensures that esm-env is inlined into the server output with the
-						// export conditions resolved correctly through Vite. This prevents adapters
-						// that bundle later on to resolve the export conditions incorrectly
-						// and for example include browser-only code in the server output
-						// because they for example use esbuild.build with `platform: 'browser'`
-						'esm-env'
-					]
-				};
-
 				if (!secondary_build_started) {
 					manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
 				}
@@ -290,13 +295,12 @@ function kit({ svelte_config }) {
 					__SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
 				};
 
-				new_config.ssr = {
-					// Without this, Vite will treat `@sveltejs/kit` as noExternal if it's
-					// a linked dependency, and that causes modules to be imported twice
-					// under different IDs, which breaks a bunch of stuff
-					// https://github.com/vitejs/vite/pull/9296
-					external: ['@sveltejs/kit', 'cookie', 'set-cookie-parser']
-				};
+				// These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
+				// Without this, the tests will still pass but `pnpm dev` will fail in projects that link `@sveltejs/kit`.
+				/** @type {NonNullable<import('vite').UserConfig['ssr']>} */ (new_config.ssr).external = [
+					'cookie',
+					'set-cookie-parser'
+				];
 			}
 
 			warn_overridden_config(config, new_config);
@@ -507,6 +511,9 @@ export function set_building() {
 					}
 				}
 
+				// see the kit.output.preloadStrategy option for details on why we have multiple options here
+				const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : 'js';
+
 				new_config = {
 					base: ssr ? assets_base(kit) : './',
 					build: {
@@ -516,12 +523,8 @@ export function set_building() {
 							input,
 							output: {
 								format: 'esm',
-								// we use .mjs for client-side modules, because this signals to Chrome (when it
-								// reads the <link rel="preload">) that it should parse the file as a module
-								// rather than as a script, preventing a double parse. Ideally we'd just use
-								// modulepreload, but Safari prevents that
-								entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].mjs`,
-								chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].mjs`,
+								entryFileNames: ssr ? '[name].js' : `${prefix}/[name].[hash].${ext}`,
+								chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name].[hash].${ext}`,
 								assetFileNames: `${prefix}/assets/[name].[hash][extname]`,
 								hoistTransitiveImports: false
 							},
diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js
index 76c34aaa4..98e4b8614 100644
--- a/packages/kit/src/exports/vite/utils.js
+++ b/packages/kit/src/exports/vite/utils.js
@@ -87,11 +87,11 @@ export function not_found(req, res, base) {
 	if (type === 'text/html') {
 		res.setHeader('Content-Type', 'text/html');
 		res.end(
-			`The server is configured with a public base URL of /path-base - did you mean to visit <a href="${prefixed}">${prefixed}</a> instead?`
+			`The server is configured with a public base URL of ${base} - did you mean to visit <a href="${prefixed}">${prefixed}</a> instead?`
 		);
 	} else {
 		res.end(
-			`The server is configured with a public base URL of /path-base - did you mean to visit ${prefixed} instead?`
+			`The server is configured with a public base URL of ${base} - did you mean to visit ${prefixed} instead?`
 		);
 	}
 }
diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js
index f6d9a52e9..8d7b14851 100644
--- a/packages/kit/src/runtime/server/data/index.js
+++ b/packages/kit/src/runtime/server/data/index.js
@@ -38,8 +38,6 @@ export async function render_data(
 		});
 	}
 
-	state.initiator = route;
-
 	try {
 		const node_ids = [...route.page.layouts, route.page.leaf];
 		const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js
index 814fe907e..a98982536 100644
--- a/packages/kit/src/runtime/server/endpoint.js
+++ b/packages/kit/src/runtime/server/endpoint.js
@@ -4,12 +4,11 @@ import { method_not_allowed } from './utils.js';
 
 /**
  * @param {import('types').RequestEvent} event
- * @param {import('types').SSRRoute} route
  * @param {import('types').SSREndpoint} mod
  * @param {import('types').SSRState} state
  * @returns {Promise<Response>}
  */
-export async function render_endpoint(event, route, mod, state) {
+export async function render_endpoint(event, mod, state) {
 	const method = /** @type {import('types').HttpMethod} */ (event.request.method);
 
 	let handler = mod[method];
@@ -29,7 +28,7 @@ export async function render_endpoint(event, route, mod, state) {
 	}
 
 	if (state.prerendering && !prerender) {
-		if (state.initiator) {
+		if (state.depth > 0) {
 			// if request came from a prerendered page, bail
 			throw new Error(`${event.route.id} is not prerenderable`);
 		} else {
@@ -39,8 +38,6 @@ export async function render_endpoint(event, route, mod, state) {
 		}
 	}
 
-	state.initiator = route;
-
 	try {
 		const response = await handler(
 			/** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js
index 2cbc013f0..e915da2c1 100644
--- a/packages/kit/src/runtime/server/fetch.js
+++ b/packages/kit/src/runtime/server/fetch.js
@@ -131,7 +131,10 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
 					);
 				}
 
-				response = await respond(request, options, manifest, state);
+				response = await respond(request, options, manifest, {
+					...state,
+					depth: state.depth + 1
+				});
 
 				const set_cookie = response.headers.get('set-cookie');
 				if (set_cookie) {
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 670a3f7f3..b9d218f9c 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -58,6 +58,10 @@ export class Server {
 			);
 		}
 
-		return respond(request, this.#options, this.#manifest, options);
+		return respond(request, this.#options, this.#manifest, {
+			...options,
+			error: false,
+			depth: 0
+		});
 	}
 }
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index aa6497eb4..c649be652 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -16,9 +16,13 @@ import { respond_with_error } from './respond_with_error.js';
 import { get_option } from '../../../utils/options.js';
 import { get_data_json } from '../data/index.js';
 
+/**
+ * The maximum request depth permitted before assuming we're stuck in an infinite loop
+ */
+const MAX_DEPTH = 10;
+
 /**
  * @param {import('types').RequestEvent} event
- * @param {import('types').SSRRoute} route
  * @param {import('types').PageNodeIndexes} page
  * @param {import('types').SSROptions} options
  * @param {import('types').SSRManifest} manifest
@@ -26,16 +30,14 @@ import { get_data_json } from '../data/index.js';
  * @param {import('types').RequiredResolveOptions} resolve_opts
  * @returns {Promise<Response>}
  */
-export async function render_page(event, route, page, options, manifest, state, resolve_opts) {
-	if (state.initiator === route) {
+export async function render_page(event, page, options, manifest, state, resolve_opts) {
+	if (state.depth > MAX_DEPTH) {
 		// infinite request cycle detected
 		return text(`Not found: ${event.url.pathname}`, {
-			status: 404
+			status: 404 // TODO in some cases this should be 500. not sure how to differentiate
 		});
 	}
 
-	state.initiator = route;
-
 	if (is_action_json_request(event)) {
 		const node = await manifest._.nodes[page.leaf]();
 		return handle_action_json_request(event, options, node?.server);
@@ -322,7 +324,7 @@ export async function render_page(event, route, page, options, manifest, state,
 			fetched
 		});
 	} catch (e) {
-		// if we end up here, it means the data loaded successfull
+		// if we end up here, it means the data loaded successfully
 		// but the page failed to render, or that a prerendering error occurred
 		return await respond_with_error({
 			event,
diff --git a/packages/kit/src/runtime/server/page/load_data.spec.js b/packages/kit/src/runtime/server/page/load_data.spec.js
index 493cbcfc5..224401e29 100644
--- a/packages/kit/src/runtime/server/page/load_data.spec.js
+++ b/packages/kit/src/runtime/server/page/load_data.spec.js
@@ -14,7 +14,7 @@ function create_fetch(event) {
 		/** @type {Pick<import('types').RequestEvent, 'fetch' | 'url' | 'request' | 'route'>} */ (
 			event
 		),
-		{ getClientAddress: () => '' },
+		{ getClientAddress: () => '', error: false, depth: 0 },
 		[],
 		true,
 		{
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 92da25ad1..ffa242e94 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -273,12 +273,13 @@ export async function render_response({
 		);
 
 		for (const path of included_modulepreloads) {
-			// we use modulepreload with the Link header for Chrome, along with
-			// <link rel="preload"> for Safari. This results in the fastest loading in
-			// the most used browsers, with no double-loading. Note that we need to use
-			// .mjs extensions for `preload` to behave like `modulepreload` in Chrome
+			// see the kit.output.preloadStrategy option for details on why we have multiple options here
 			link_header_preloads.add(`<${encodeURI(path)}>; rel="modulepreload"; nopush`);
-			head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
+			if (options.preload_strategy !== 'modulepreload') {
+				head += `\n\t\t<link rel="preload" as="script" crossorigin="anonymous" href="${path}">`;
+			} else if (state.prerendering) {
+				head += `\n\t\t<link rel="modulepreload" href="${path}">`;
+			}
 		}
 
 		const blocks = [];
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 591100446..10607a09d 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -1,11 +1,6 @@
 import { render_response } from './render.js';
 import { load_data, load_server_data } from './load_data.js';
-import {
-	handle_error_and_jsonify,
-	static_error_page,
-	redirect_response,
-	GENERIC_ERROR
-} from '../utils.js';
+import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
 import { get_option } from '../../../utils/options.js';
 import { HttpError, Redirect } from '../../control.js';
 
@@ -43,7 +38,7 @@ export async function respond_with_error({
 		const csr = get_option([default_layout], 'csr') ?? true;
 
 		if (ssr) {
-			state.initiator = GENERIC_ERROR;
+			state.error = true;
 
 			const server_data_promise = load_server_data({
 				event,
diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js
index ce2c9f0f4..0707898df 100644
--- a/packages/kit/src/runtime/server/respond.js
+++ b/packages/kit/src/runtime/server/respond.js
@@ -5,7 +5,7 @@ import { render_page } from './page/index.js';
 import { render_response } from './page/render.js';
 import { respond_with_error } from './page/respond_with_error.js';
 import { is_form_content_type } from '../../utils/http.js';
-import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
+import { handle_fatal_error, redirect_response } from './utils.js';
 import {
 	decode_pathname,
 	decode_params,
@@ -38,7 +38,13 @@ const default_filter = () => false;
 /** @type {import('types').RequiredResolveOptions['preload']} */
 const default_preload = ({ type }) => type === 'js' || type === 'css';
 
-/** @type {import('types').Respond} */
+/**
+ * @param {Request} request
+ * @param {import('types').SSROptions} options
+ * @param {import('types').SSRManifest} manifest
+ * @param {import('types').SSRState} state
+ * @returns {Promise<Response>}
+ */
 export async function respond(request, options, manifest, state) {
 	/** URL but stripped from the potential `/__data.json` suffix and its search param  */
 	let url = new URL(request.url);
@@ -358,17 +364,9 @@ export async function respond(request, options, manifest, state) {
 						trailing_slash ?? 'never'
 					);
 				} else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
-					response = await render_endpoint(event, route, await route.endpoint(), state);
+					response = await render_endpoint(event, await route.endpoint(), state);
 				} else if (route.page) {
-					response = await render_page(
-						event,
-						route,
-						route.page,
-						options,
-						manifest,
-						state,
-						resolve_opts
-					);
+					response = await render_page(event, route.page, options, manifest, state, resolve_opts);
 				} else {
 					// a route will always have a page or an endpoint, but TypeScript
 					// doesn't know that
@@ -378,15 +376,15 @@ export async function respond(request, options, manifest, state) {
 				return response;
 			}
 
-			if (state.initiator === GENERIC_ERROR) {
+			if (state.error) {
 				return text('Internal Server Error', {
 					status: 500
 				});
 			}
 
 			// if this request came direct from the user, rather than
-			// via a `fetch` in a `load`, render a 404 page
-			if (!state.initiator) {
+			// via our own `fetch`, render a 404 page
+			if (state.depth === 0) {
 				return await respond_with_error({
 					event,
 					options,
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index 54bdc7c87..7dd82e764 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -16,11 +16,6 @@ export function is_pojo(body) {
 	return true;
 }
 
-/** @type {import('types').SSRErrorPage} */
-export const GENERIC_ERROR = {
-	id: '__error'
-};
-
 /**
  * @param {Partial<Record<import('types').HttpMethod, any>>} mod
  * @param {import('types').HttpMethod} method
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 73c5683ca..2735f060e 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -422,6 +422,20 @@ export interface KitConfig {
 	 * @default ".svelte-kit"
 	 */
 	outDir?: string;
+	/**
+	 * Options related to the build output format
+	 */
+	output?: {
+		/**
+		 * SvelteKit will preload the JavaScript modules needed for the initial page to avoid import 'waterfalls', resulting in faster application startup. There
+		 * are three strategies with different trade-offs:
+		 * - `modulepreload` - uses `<link rel="modulepreload">`. This delivers the best results in Chromium-based browsers, but is currently ignored by Firefox and Safari (though support is coming to Safari soon).
+		 * - `preload-js` - uses `<link rel="preload">`. Prevents waterfalls in Chromium and Safari, but Chromium will parse each module twice (once as a script, once as a module). Causes modules to be requested twice in Firefox. This is a good setting if you want to maximise performance for users on iOS devices at the cost of a very slight degradation for Chromium users.
+		 * - `preload-mjs` - uses `<link rel="preload">` but with the `.mjs` extension which prevents double-parsing in Chromium. Some static webservers will fail to serve .mjs files with a `Content-Type: application/javascript` header, which will cause your application to break. If that doesn't apply to you, this is the option that will deliver the best performance for the largest number of users, until `modulepreload` is more widely supported.
+		 * @default "modulepreload"
+		 */
+		preloadStrategy?: 'modulepreload' | 'preload-js' | 'preload-mjs';
+	};
 	paths?: {
 		/**
 		 * An absolute path that your app's files are served from. This is useful if your files are served from a storage bucket of some kind.
@@ -525,7 +539,7 @@ export interface KitConfig {
 	 * Client-side navigation can be buggy if you deploy a new version of your app while people are using it. If the code for the new page is already loaded, it may have stale content; if it isn't, the app's route manifest may point to a JavaScript file that no longer exists.
 	 * SvelteKit helps you solve this problem through version management.
 	 * If SvelteKit encounters an error while loading the page and detects that a new version has been deployed (using the `name` specified here, which defaults to a timestamp of the build) it will fall back to traditional full-page navigation.
-	 * Not all navigations will result in an error though, for example if the JavaScript for the next page is already loaded. If you still want to force a full-page navigation in these cases, use techniques such as setting the `pollInverval` and then using `beforeNavigate`:
+	 * Not all navigations will result in an error though, for example if the JavaScript for the next page is already loaded. If you still want to force a full-page navigation in these cases, use techniques such as setting the `pollInterval` and then using `beforeNavigate`:
 	 * ```html
 	 * /// +layout.svelte
 	 * <script>
@@ -544,7 +558,7 @@ 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
 		 */
 		name?: string;
 		/**
@@ -892,7 +906,7 @@ export interface RequestEvent<
 	 */
 	locals: App.Locals;
 	/**
-	 * The parameters of the current page or endpoint - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object
+	 * The parameters of the current route - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object
 	 */
 	params: Params;
 	/**
@@ -936,7 +950,7 @@ export interface RequestEvent<
 	 */
 	setHeaders(headers: Record<string, string>): void;
 	/**
-	 * The URL of the current page or endpoint.
+	 * The requested URL.
 	 */
 	url: URL;
 	/**
@@ -983,6 +997,12 @@ export interface ResolveOptions {
 
 export interface RouteDefinition<Config = any> {
 	id: string;
+	api: {
+		methods: HttpMethod[];
+	};
+	page: {
+		methods: Extract<HttpMethod, 'GET' | 'POST'>[];
+	};
 	pattern: RegExp;
 	prerender: PrerenderOption;
 	segments: RouteSegment[];
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index bad1376c0..363be1f45 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -157,15 +157,6 @@ export type RecursiveRequired<T> = {
 
 export type RequiredResolveOptions = Required<ResolveOptions>;
 
-export interface Respond {
-	(
-		request: Request,
-		options: SSROptions,
-		manifest: SSRManifest,
-		state: SSRState
-	): Promise<Response>;
-}
-
 export interface RouteParam {
 	name: string;
 	matcher: string;
@@ -263,16 +254,21 @@ export interface ServerErrorNode {
 	status?: number;
 }
 
+export interface ServerMetadataRoute {
+	config: any;
+	api: {
+		methods: HttpMethod[];
+	};
+	page: {
+		methods: Array<'GET' | 'POST'>;
+	};
+	methods: HttpMethod[];
+	prerender: PrerenderOption | undefined;
+}
+
 export interface ServerMetadata {
 	nodes: Array<{ has_server_load: boolean }>;
-	routes: Map<
-		string,
-		{
-			prerender: PrerenderOption | undefined;
-			methods: HttpMethod[];
-			config: any;
-		}
-	>;
+	routes: Map<string, ServerMetadataRoute>;
 }
 
 export interface SSRComponent {
@@ -337,6 +333,7 @@ export interface SSROptions {
 	embedded: boolean;
 	env_public_prefix: string;
 	hooks: ServerHooks;
+	preload_strategy: ValidatedConfig['kit']['output']['preloadStrategy'];
 	root: SSRComponent['default'];
 	service_worker: boolean;
 	templates: {
@@ -352,10 +349,6 @@ export interface SSROptions {
 	version_hash: string;
 }
 
-export interface SSRErrorPage {
-	id: '__error';
-}
-
 export interface PageNodeIndexes {
 	errors: Array<number | undefined>;
 	layouts: Array<number | undefined>;
@@ -380,7 +373,14 @@ export interface SSRRoute {
 export interface SSRState {
 	fallback?: string;
 	getClientAddress(): string;
-	initiator?: SSRRoute | SSRErrorPage;
+	/**
+	 * True if we're currently attempting to render an error page
+	 */
+	error: boolean;
+	/**
+	 * Allows us to prevent `event.fetch` from making infinitely looping internal requests
+	 */
+	depth: number;
 	platform?: any;
 	prerendering?: PrerenderOptions;
 	/**
diff --git a/packages/migrate/CHANGELOG.md b/packages/migrate/CHANGELOG.md
index 31b8aec2b..d1af45a4d 100644
--- a/packages/migrate/CHANGELOG.md
+++ b/packages/migrate/CHANGELOG.md
@@ -1,5 +1,11 @@
 # svelte-migrate
 
+## 1.1.3
+
+### Patch Changes
+
+- fix: include index in typesVersions because it's always matched ([#9147](https://github.com/sveltejs/kit/pull/9147))
+
 ## 1.1.2
 
 ### Patch Changes
diff --git a/packages/migrate/bin.js b/packages/migrate/bin.js
index d8128ae79..80704a57c 100755
--- a/packages/migrate/bin.js
+++ b/packages/migrate/bin.js
@@ -1,6 +1,6 @@
 #!/usr/bin/env node
-import fs from 'fs';
-import { fileURLToPath } from 'url';
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
 import colors from 'kleur';
 
 const migration = process.argv[2];
diff --git a/packages/migrate/migrations/package/index.js b/packages/migrate/migrations/package/index.js
index ff193a25c..f4d436644 100644
--- a/packages/migrate/migrations/package/index.js
+++ b/packages/migrate/migrations/package/index.js
@@ -1,8 +1,8 @@
-import fs from 'fs';
+import fs from 'node:fs';
 import colors from 'kleur';
-import path from 'path';
+import path from 'node:path';
 import prompts from 'prompts';
-import { pathToFileURL } from 'url';
+import { pathToFileURL } from 'node:url';
 import { bail, check_git } from '../../utils.js';
 import { migrate_config } from './migrate_config.js';
 import { migrate_pkg } from './migrate_pkg.js';
diff --git a/packages/migrate/migrations/package/migrate_config.js b/packages/migrate/migrations/package/migrate_config.js
index 63e5df62d..b401626d4 100644
--- a/packages/migrate/migrations/package/migrate_config.js
+++ b/packages/migrate/migrations/package/migrate_config.js
@@ -1,4 +1,4 @@
-import fs from 'fs';
+import fs from 'node:fs';
 import colors from 'kleur';
 import MagicString from 'magic-string';
 import ts from 'typescript';
diff --git a/packages/migrate/migrations/package/migrate_pkg.js b/packages/migrate/migrations/package/migrate_pkg.js
index 954bbb363..611430938 100644
--- a/packages/migrate/migrations/package/migrate_pkg.js
+++ b/packages/migrate/migrations/package/migrate_pkg.js
@@ -113,15 +113,16 @@ export function update_pkg_json(config, pkg, files) {
 				file.is_svelte ? `${file.dest}.d.ts` : file.dest.slice(0, -'.js'.length) + '.d.ts'
 			}`;
 
-			if (has_type && key.slice(2) /* don't add root index type */) {
+			if (has_type) {
+				const type_key = key.slice(2) || 'index';
 				if (!pkg.exports[key]) {
-					types_versions[key.slice(2)] = [out_dir_type_path];
+					types_versions[type_key] = [out_dir_type_path];
 				} else {
 					const path_without_ext = pkg.exports[key].slice(
 						0,
 						-path.extname(pkg.exports[key]).length
 					);
-					types_versions[key.slice(2)] = [
+					types_versions[type_key] = [
 						`./${out_dir}/${(pkg.exports[key].types ?? path_without_ext + '.d.ts').slice(2)}`
 					];
 				}
@@ -188,7 +189,10 @@ export function update_pkg_json(config, pkg, files) {
 
 	// https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions
 	// A hack to get around the limitation that TS doesn't support "exports" field  with moduleResolution: 'node'
-	if (Object.keys(types_versions).length > 0) {
+	if (
+		Object.keys(types_versions).length > 1 ||
+		(Object.keys(types_versions).length > 0 && !types_versions['index'])
+	) {
 		pkg.typesVersions = { '>4.0': types_versions };
 	}
 
diff --git a/packages/migrate/migrations/package/migrate_pkg.spec.js b/packages/migrate/migrations/package/migrate_pkg.spec.js
index 933234225..43e0db640 100644
--- a/packages/migrate/migrations/package/migrate_pkg.spec.js
+++ b/packages/migrate/migrations/package/migrate_pkg.spec.js
@@ -95,6 +95,7 @@ test('Updates package.json', () => {
 		svelte: './package/index.js',
 		typesVersions: {
 			'>4.0': {
+				index: ['./package/index.d.ts'],
 				'foo/Bar.svelte': ['./package/foo/Bar.svelte.d.ts'],
 				baz: ['./package/baz.d.ts'],
 				bar: ['./package/bar/index.d.ts'],
diff --git a/packages/migrate/migrations/routes/index.js b/packages/migrate/migrations/routes/index.js
index a6568e531..94358ff4a 100644
--- a/packages/migrate/migrations/routes/index.js
+++ b/packages/migrate/migrations/routes/index.js
@@ -1,9 +1,9 @@
-import fs from 'fs';
+import fs from 'node:fs';
 import colors from 'kleur';
-import path from 'path';
+import path from 'node:path';
 import prompts from 'prompts';
 import glob from 'tiny-glob/sync.js';
-import { pathToFileURL } from 'url';
+import { pathToFileURL } from 'node:url';
 import { migrate_scripts } from './migrate_scripts/index.js';
 import { migrate_page } from './migrate_page_js/index.js';
 import { migrate_page_server } from './migrate_page_server/index.js';
diff --git a/packages/migrate/migrations/routes/utils.js b/packages/migrate/migrations/routes/utils.js
index 6bfc40f01..28ca024be 100644
--- a/packages/migrate/migrations/routes/utils.js
+++ b/packages/migrate/migrations/routes/utils.js
@@ -1,4 +1,4 @@
-import fs from 'fs';
+import fs from 'node:fs';
 import ts from 'typescript';
 import MagicString from 'magic-string';
 import { comment, indent_at_line } from '../../utils.js';
diff --git a/packages/migrate/package.json b/packages/migrate/package.json
index d5c2c94b5..a57a49152 100644
--- a/packages/migrate/package.json
+++ b/packages/migrate/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "svelte-migrate",
-	"version": "1.1.2",
+	"version": "1.1.3",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -24,7 +24,7 @@
 	],
 	"dependencies": {
 		"kleur": "^4.1.5",
-		"magic-string": "^0.29.0",
+		"magic-string": "^0.30.0",
 		"prompts": "^2.4.2",
 		"tiny-glob": "^0.2.9",
 		"typescript": "^4.9.4"
diff --git a/packages/package/src/filesystem.js b/packages/package/src/filesystem.js
index a1edf04d2..04efd95a4 100644
--- a/packages/package/src/filesystem.js
+++ b/packages/package/src/filesystem.js
@@ -1,5 +1,5 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
 
 /** @param {string} dir */
 export function mkdirp(dir) {
diff --git a/packages/package/src/typescript.js b/packages/package/src/typescript.js
index db91a6529..4b8a282bf 100644
--- a/packages/package/src/typescript.js
+++ b/packages/package/src/typescript.js
@@ -1,6 +1,6 @@
-import * as fs from 'fs';
-import * as path from 'path';
-import { createRequire } from 'module';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+import { createRequire } from 'node:module';
 import { posixify, mkdirp, rimraf, walk } from './filesystem.js';
 import { resolve_aliases, write } from './utils.js';
 import { emitDts } from 'svelte2tsx';
@@ -29,9 +29,8 @@ export async function emit_dts(input, output, cwd, alias, files) {
 	});
 
 	const handwritten = new Set();
-	const excluded = new Set();
 
-	// remove excluded files, and files that conflict with hand-written .d.ts
+	// skip files that conflict with hand-written .d.ts
 	for (const file of files) {
 		if (file.name.endsWith('.d.ts')) {
 			handwritten.add(file.name);
@@ -46,9 +45,6 @@ export async function emit_dts(input, output, cwd, alias, files) {
 			console.warn(`Using $lib/${normalized} instead of generated .d.ts file`);
 		}
 
-		// don't overwrite hand-written .d.ts files
-		if (excluded.has(normalized)) continue;
-
 		const source = fs.readFileSync(path.join(tmp, normalized), 'utf8');
 		write(path.join(output, normalized), resolve_aliases(input, normalized, source, alias));
 	}
diff --git a/packages/package/src/utils.js b/packages/package/src/utils.js
index 7526c10af..26605fbbd 100644
--- a/packages/package/src/utils.js
+++ b/packages/package/src/utils.js
@@ -1,5 +1,5 @@
-import * as fs from 'fs';
-import * as path from 'path';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
 import { posixify, mkdirp, walk } from './filesystem.js';
 
 /**
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5474de151..ecd8afeeb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,6 +9,7 @@ importers:
       '@rollup/plugin-json': ^6.0.0
       '@rollup/plugin-node-resolve': ^15.0.1
       '@svitejs/changesets-changelog-github-compact': ^1.1.0
+      '@typescript-eslint/eslint-plugin': ^5.53.0
       eslint: ^8.33.0
       eslint-plugin-unicorn: ^45.0.2
       playwright: ^1.29.2
@@ -23,6 +24,7 @@ importers:
       '@rollup/plugin-json': 6.0.0_rollup@3.7.0
       '@rollup/plugin-node-resolve': 15.0.1_rollup@3.7.0
       '@svitejs/changesets-changelog-github-compact': 1.1.0
+      '@typescript-eslint/eslint-plugin': 5.53.0_zkdaqh7it7uc4cvz2haft7rc6u
       eslint: 8.33.0
       eslint-plugin-unicorn: 45.0.2_eslint@8.33.0
       playwright: 1.29.2
@@ -205,6 +207,7 @@ importers:
 
   packages/create-svelte:
     specifiers:
+      '@clack/prompts': ^0.6.0
       '@playwright/test': ^1.28.1
       '@types/gitignore-parser': ^0.0.0
       '@types/prettier': ^2.7.1
@@ -213,14 +216,13 @@ importers:
       kleur: ^4.1.5
       prettier: ^2.8.0
       prettier-plugin-svelte: ^2.8.1
-      prompts: ^2.4.2
       sucrase: ^3.29.0
       svelte: ^3.55.1
       tiny-glob: ^0.2.9
       uvu: ^0.5.6
     dependencies:
+      '@clack/prompts': 0.6.0
       kleur: 4.1.5
-      prompts: 2.4.2
     devDependencies:
       '@playwright/test': 1.29.2
       '@types/gitignore-parser': 0.0.0
@@ -274,7 +276,7 @@ importers:
       devalue: ^4.3.0
       esm-env: ^1.0.0
       kleur: ^4.1.5
-      magic-string: ^0.29.0
+      magic-string: ^0.30.0
       marked: ^4.2.3
       mime: ^3.0.0
       rollup: ^3.7.0
@@ -295,7 +297,7 @@ importers:
       devalue: 4.3.0
       esm-env: 1.0.0
       kleur: 4.1.5
-      magic-string: 0.29.0
+      magic-string: 0.30.0
       mime: 3.0.0
       sade: 1.8.1
       set-cookie-parser: 2.5.1
@@ -658,7 +660,7 @@ importers:
     specifiers:
       '@types/prompts': ^2.4.1
       kleur: ^4.1.5
-      magic-string: ^0.29.0
+      magic-string: ^0.30.0
       prettier: ^2.8.0
       prompts: ^2.4.2
       tiny-glob: ^0.2.9
@@ -666,7 +668,7 @@ importers:
       uvu: ^0.5.6
     dependencies:
       kleur: 4.1.5
-      magic-string: 0.29.0
+      magic-string: 0.30.0
       prompts: 2.4.2
       tiny-glob: 0.2.9
       typescript: 4.9.4
@@ -709,7 +711,7 @@ importers:
       d3-geo: ^3.0.1
       d3-geo-projection: ^4.0.0
       flexsearch: ^0.7.31
-      magic-string: ^0.29.0
+      magic-string: ^0.30.0
       marked: ^4.2.3
       prism-svelte: ^0.5.0
       prismjs: ^1.29.0
@@ -733,7 +735,7 @@ importers:
       '@types/d3-geo': 3.0.2
       '@types/node': 16.18.6
       flexsearch: 0.7.31
-      magic-string: 0.29.0
+      magic-string: 0.30.0
       marked: 4.2.3
       prism-svelte: 0.5.0
       prismjs: 1.29.0
@@ -971,6 +973,23 @@ packages:
       prettier: 2.8.0
     dev: true
 
+  /@clack/core/0.3.0:
+    resolution: {integrity: sha512-ujw1888RciTArxUvwLOf24XSygRX7F4qiCPI7WLH3zCTZJuqKPMcTS7Wqjz0x/AuMpwGPlzhKln4+sCuQqYxzA==}
+    dependencies:
+      picocolors: 1.0.0
+      sisteransi: 1.0.5
+    dev: false
+
+  /@clack/prompts/0.6.0:
+    resolution: {integrity: sha512-lNg3MFPLA2rrMAZ6R2QFg+jGo2Pt3ACJERDbPgxjPU9QJwVFB3D2v3x7SzzVoS6schggcMN0tDJ4zSdcI+kqJg==}
+    dependencies:
+      '@clack/core': 0.3.0
+      picocolors: 1.0.0
+      sisteransi: 1.0.5
+    dev: false
+    bundledDependencies:
+      - is-unicode-supported
+
   /@cloudflare/kv-asset-handler/0.3.0:
     resolution: {integrity: sha512-9CB/MKf/wdvbfkUdfrj+OkEwZ5b7rws0eogJ4293h+7b6KX5toPwym+VQKmILafNB9YiehqY0DlNrDcDhdWHSQ==}
     dependencies:
@@ -1656,6 +1675,10 @@ packages:
     resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
     dev: true
 
+  /@types/json-schema/7.0.11:
+    resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
+    dev: true
+
   /@types/marked/4.0.7:
     resolution: {integrity: sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==}
     dev: true
@@ -1720,6 +1743,10 @@ packages:
     resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==}
     dev: true
 
+  /@types/semver/7.3.13:
+    resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
+    dev: true
+
   /@types/set-cookie-parser/2.4.2:
     resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==}
     dependencies:
@@ -1732,6 +1759,115 @@ packages:
       '@types/node': 16.18.6
     dev: true
 
+  /@typescript-eslint/eslint-plugin/5.53.0_zkdaqh7it7uc4cvz2haft7rc6u:
+    resolution: {integrity: sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^5.0.0
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/scope-manager': 5.53.0
+      '@typescript-eslint/type-utils': 5.53.0_zkdaqh7it7uc4cvz2haft7rc6u
+      '@typescript-eslint/utils': 5.53.0_zkdaqh7it7uc4cvz2haft7rc6u
+      debug: 4.3.4
+      eslint: 8.33.0
+      grapheme-splitter: 1.0.4
+      ignore: 5.2.0
+      natural-compare-lite: 1.4.0
+      regexpp: 3.2.0
+      semver: 7.3.8
+      tsutils: 3.21.0_typescript@4.9.4
+      typescript: 4.9.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/scope-manager/5.53.0:
+    resolution: {integrity: sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.53.0
+      '@typescript-eslint/visitor-keys': 5.53.0
+    dev: true
+
+  /@typescript-eslint/type-utils/5.53.0_zkdaqh7it7uc4cvz2haft7rc6u:
+    resolution: {integrity: sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 5.53.0_typescript@4.9.4
+      '@typescript-eslint/utils': 5.53.0_zkdaqh7it7uc4cvz2haft7rc6u
+      debug: 4.3.4
+      eslint: 8.33.0
+      tsutils: 3.21.0_typescript@4.9.4
+      typescript: 4.9.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/types/5.53.0:
+    resolution: {integrity: sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
+  /@typescript-eslint/typescript-estree/5.53.0_typescript@4.9.4:
+    resolution: {integrity: sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 5.53.0
+      '@typescript-eslint/visitor-keys': 5.53.0
+      debug: 4.3.4
+      globby: 11.1.0
+      is-glob: 4.0.3
+      semver: 7.3.8
+      tsutils: 3.21.0_typescript@4.9.4
+      typescript: 4.9.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/utils/5.53.0_zkdaqh7it7uc4cvz2haft7rc6u:
+    resolution: {integrity: sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      '@types/json-schema': 7.0.11
+      '@types/semver': 7.3.13
+      '@typescript-eslint/scope-manager': 5.53.0
+      '@typescript-eslint/types': 5.53.0
+      '@typescript-eslint/typescript-estree': 5.53.0_typescript@4.9.4
+      eslint: 8.33.0
+      eslint-scope: 5.1.1
+      eslint-utils: 3.0.0_eslint@8.33.0
+      semver: 7.3.8
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
+  /@typescript-eslint/visitor-keys/5.53.0:
+    resolution: {integrity: sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.53.0
+      eslint-visitor-keys: 3.3.0
+    dev: true
+
   /@typescript/twoslash/3.1.0:
     resolution: {integrity: sha512-kTwMUQ8xtAZaC4wb2XuLkPqFVBj2dNBueMQ89NWEuw87k2nLBbuafeG5cob/QEr6YduxIdTVUjix0MtC7mPlmg==}
     dependencies:
@@ -2592,6 +2728,14 @@ packages:
       strip-indent: 3.0.0
     dev: true
 
+  /eslint-scope/5.1.1:
+    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+    engines: {node: '>=8.0.0'}
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 4.3.0
+    dev: true
+
   /eslint-scope/7.1.1:
     resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2701,6 +2845,11 @@ packages:
       estraverse: 5.3.0
     dev: true
 
+  /estraverse/4.3.0:
+    resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+    engines: {node: '>=4.0'}
+    dev: true
+
   /estraverse/5.3.0:
     resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
     engines: {node: '>=4.0'}
@@ -3563,8 +3712,8 @@ packages:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.14
 
-  /magic-string/0.29.0:
-    resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==}
+  /magic-string/0.30.0:
+    resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
     engines: {node: '>=12'}
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.14
@@ -3736,6 +3885,10 @@ packages:
     resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
     dev: true
 
+  /natural-compare-lite/1.4.0:
+    resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+    dev: true
+
   /natural-compare/1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
@@ -5012,10 +5165,24 @@ packages:
     resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
     dev: true
 
+  /tslib/1.14.1:
+    resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+    dev: true
+
   /tslib/2.4.1:
     resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
     dev: false
 
+  /tsutils/3.21.0_typescript@4.9.4:
+    resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+    engines: {node: '>= 6'}
+    peerDependencies:
+      typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+    dependencies:
+      tslib: 1.14.1
+      typescript: 4.9.4
+    dev: true
+
   /tty-table/4.1.6:
     resolution: {integrity: sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==}
     engines: {node: '>=8.0.0'}
diff --git a/sites/kit.svelte.dev/package.json b/sites/kit.svelte.dev/package.json
index adcd13502..68bab4a46 100644
--- a/sites/kit.svelte.dev/package.json
+++ b/sites/kit.svelte.dev/package.json
@@ -18,7 +18,7 @@
 		"@types/d3-geo": "^3.0.2",
 		"@types/node": "^16.18.6",
 		"flexsearch": "^0.7.31",
-		"magic-string": "^0.29.0",
+		"magic-string": "^0.30.0",
 		"marked": "^4.2.3",
 		"prism-svelte": "^0.5.0",
 		"prismjs": "^1.29.0",
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 e8a4ea539..604de5301 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -36,8 +36,7 @@ const base = '../../documentation';
 
 const type_regex = new RegExp(
 	`(import\\(&apos;@sveltejs\\/kit&apos;\\)\\.)?\\b(${modules
-		.map((module) => module.types)
-		.flat()
+		.flatMap((module) => module.types)
 		.map((type) => type.name)
 		.join('|')})\\b`,
 	'g'
diff --git a/sites/kit.svelte.dev/src/lib/search/search.js b/sites/kit.svelte.dev/src/lib/search/search.js
index 8f834aec5..d4a3f02e3 100644
--- a/sites/kit.svelte.dev/src/lib/search/search.js
+++ b/sites/kit.svelte.dev/src/lib/search/search.js
@@ -51,8 +51,7 @@ export function search(query) {
 	const regex = new RegExp(`(^|\\b)${escaped}`, 'i');
 
 	const blocks = indexes
-		.map((index) => index.search(query))
-		.flat()
+		.flatMap((index) => index.search(query))
 		.map(lookup)
 		.map((block, rank) => ({ block, rank }))
 		.sort((a, b) => {
diff --git a/sites/kit.svelte.dev/src/routes/+layout.svelte b/sites/kit.svelte.dev/src/routes/+layout.svelte
index beac48120..ba071448a 100644
--- a/sites/kit.svelte.dev/src/routes/+layout.svelte
+++ b/sites/kit.svelte.dev/src/routes/+layout.svelte
@@ -12,6 +12,8 @@
 	import Search from '$lib/search/Search.svelte';
 	import SearchBox from '$lib/search/SearchBox.svelte';
 	import Logo from './home/svelte-logo.svg';
+
+	let banner_height = '48px';
 </script>
 
 <Icons />
@@ -33,10 +35,13 @@
 	</svelte:fragment>
 
 	<svelte:fragment slot="nav-right">
-		<NavItem selected={$page.url.pathname.startsWith(`${base}/docs`) || undefined} href="{base}/docs"
-			>Docs</NavItem
+		<NavItem
+			selected={$page.url.pathname.startsWith(`${base}/docs`) || undefined}
+			href="{base}/docs">Docs</NavItem
+		>
+		<NavItem selected={$page.url.pathname.startsWith(`${base}/faq`) || undefined} href="{base}/faq"
+			>FAQ</NavItem
 		>
-		<NavItem selected={$page.url.pathname.startsWith(`${base}/faq`) || undefined} href="{base}/faq">FAQ</NavItem>
 
 		<li aria-hidden="true"><span class="separator" /></li>
 
@@ -54,19 +59,38 @@
 	</svelte:fragment>
 </Nav>
 
-<main id="main">
+<main id="main" style:--banner-footer-height={banner_height}>
 	<slot />
 </main>
 
+{#if banner_height !== '0px'}
+	<div class="banner" style:--banner-footer-height={banner_height}>
+		<a target="_blank" rel="noopener noreferrer" href="https://hack.sveltesociety.dev/">
+			<span class="small">
+				<strong>Announcing SvelteHack</strong> Participate →
+			</span>
+			<span class="large">
+				<strong>Announcing SvelteHack</strong> Our first hackathon with over $12,000 in prizes →
+			</span>
+		</a>
+		<button on:click={() => (banner_height = '0px')}> ✕ </button>
+	</div>
+{/if}
+
 {#if browser}
 	<SearchBox />
 {/if}
 
 <style>
+	:root {
+		--banner-footer-height: 48px;
+	}
+
 	main {
 		position: relative;
 		margin: 0 auto;
 		padding-top: var(--sk-nav-height);
+		padding-bottom: var(--banner-footer-height);
 		overflow: hidden;
 	}
 
@@ -144,4 +168,35 @@
 			z-index: 9999 !important;
 		}
 	}
+
+	.banner {
+		--banner-bg: #ff4700;
+		--banner-color: white;
+		--banner-strong-color: white;
+
+		background-color: var(--banner-bg);
+		color: var(--banner-color);
+		position: fixed;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		bottom: 0;
+		width: 100vw;
+		height: var(--banner-footer-height);
+		z-index: 999;
+	}
+
+	.banner strong {
+		font-weight: bold;
+	}
+
+	.banner span {
+		color: var(--banner-color);
+	}
+
+	.banner button {
+		position: absolute;
+		right: 30px;
+		font-size: 18px;
+	}
 </style>
diff --git a/sites/kit.svelte.dev/src/routes/+page.svelte b/sites/kit.svelte.dev/src/routes/+page.svelte
index d3ff1ae9a..7f297b1c8 100644
--- a/sites/kit.svelte.dev/src/routes/+page.svelte
+++ b/sites/kit.svelte.dev/src/routes/+page.svelte
@@ -1,4 +1,5 @@
 <script>
+	import { onMount } from 'svelte';
 	import Features from './home/Features.svelte';
 	import Hero from './home/Hero.svelte';
 	import Showcase from './home/Showcase.svelte';
@@ -6,7 +7,16 @@
 	import Deployment from './home/Deployment.svelte';
 	import Svelte from './home/Svelte.svelte';
 	import Intro from './home/Intro.svelte';
+	import schema_url from './schema.json?url';
 	import './home/common.css';
+
+	let schema;
+	onMount(async () => {
+		// Google will not allow linking to a schema file. It must be in the DOM
+		// We load it and add it to the DOM to save bytes on page load vs inlining
+		const json = await (await fetch(schema_url)).text();
+		schema = `<script type="application/ld+json">${json}<\/script>`;
+	});
 </script>
 
 <svelte:head>
@@ -15,6 +25,22 @@
 	<meta name="twitter:title" content="SvelteKit" />
 	<meta name="twitter:description" content="Web development, streamlined" />
 	<meta name="description" content="SvelteKit is the official Svelte application framework" />
+
+	<meta property="og:type" content="website" />
+	<meta property="og:title" content="SvelteKit • Web development, streamlined" />
+	<meta
+		property="og:description"
+		content="SvelteKit is the official Svelte application framework"
+	/>
+	<meta property="og:url" content="https://kit.svelte.dev/" />
+	<meta
+		property="og:image"
+		content="https://raw.githubusercontent.com/sveltejs/branding/master/svelte-logo.svg"
+	/>
+
+	{#if schema}
+		{@html schema}
+	{/if}
 </svelte:head>
 
 <div class="home">
diff --git a/sites/kit.svelte.dev/src/routes/docs/+layout.svelte b/sites/kit.svelte.dev/src/routes/docs/+layout.svelte
index 13c99abb0..cff5b63c7 100644
--- a/sites/kit.svelte.dev/src/routes/docs/+layout.svelte
+++ b/sites/kit.svelte.dev/src/routes/docs/+layout.svelte
@@ -45,7 +45,7 @@
 	@media (min-width: 832px) {
 		.toc-container {
 			width: var(--sidebar-width);
-			height: calc(100vh - var(--sk-nav-height) - var(--ts-toggle-height));
+			height: calc(100vh - var(--sk-nav-height) - var(--ts-toggle-height) - var(--banner-footer-height));
 			position: fixed;
 			left: 0;
 			top: var(--sk-nav-height);
@@ -70,7 +70,7 @@
 		.ts-toggle {
 			position: fixed;
 			width: var(--sidebar-width);
-			bottom: 0;
+			bottom: var(--banner-footer-height);
 			z-index: 1;
 			margin-right: 0;
 			border-right: 1px solid var(--sk-back-5);
diff --git a/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte b/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte
index 4a0fe361c..696541b8a 100644
--- a/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte
+++ b/sites/kit.svelte.dev/src/routes/docs/[slug]/+page.svelte
@@ -7,7 +7,7 @@
 	/** @type {import('./$types').PageData} */
 	export let data;
 
-	$: pages = data.sections.map((section) => section.pages).flat();
+	$: pages = data.sections.flatMap((section) => section.pages);
 	$: index = pages.findIndex(({ path }) => path === $page.url.pathname);
 	$: prev = pages[index - 1];
 	$: next = pages[index + 1];
diff --git a/sites/kit.svelte.dev/src/routes/schema.json b/sites/kit.svelte.dev/src/routes/schema.json
new file mode 100644
index 000000000..1cba9f643
--- /dev/null
+++ b/sites/kit.svelte.dev/src/routes/schema.json
@@ -0,0 +1,98 @@
+{
+	"@context": "http://schema.org",
+	"@type": "SoftwareSourceCode",
+	"name": "SvelteKit",
+	"description": "SvelteKit is the official Svelte application framework.",
+	"url": "https://kit.svelte.dev/",
+	"codeRepository": "https://github.com/sveltejs/kit",
+	"dateCreated": "2020-10-15T16:00:00",
+	"programmingLanguage": [
+		"JavaScript",
+		"TypeScript"
+	],
+	"image": "https://raw.githubusercontent.com/sveltejs/branding/master/svelte-logo.svg",
+	"sameAs": [
+		"https://github.com/sveltejs/kit",
+		"https://www.npmjs.com/package/@sveltejs/kit"
+	],
+	"license": "https://github.com/sveltejs/kit/blob/master/LICENSE",
+	"releasedEvent": {
+		"@type": "PublicationEvent",
+		"startDate": "2022-12-14T17:00:00",
+		"url": [
+			"https://svelte.dev/blog/announcing-sveltekit-1.0",
+			"https://www.youtube.com/watch?v=N4BRVkQVoMc"
+		]
+	},
+	"contributor": [{
+		"@type": "Person",
+		"name": "Rich Harris",
+		"url": "https://www.rich-harris.co.uk",
+		"image": "https://avatars.githubusercontent.com/u/1162160",
+		"sameAs": [
+			"https://github.com/Rich-Harris",
+			"https://twitter.com/Rich_Harris"
+		]	
+	}, {
+		"@type": "Person",
+		"name": "Ben McCann",
+		"url": "https://www.benmccann.com",
+		"image": "https://avatars.githubusercontent.com/u/322311",
+		"sameAs": [
+			"https://github.com/benmccann",
+			"https://twitter.com/benjaminmccann",
+			"https://about.me/benmccann",
+			"https://angel.co/ben-mccann",
+			"https://www.crunchbase.com/person/benjamin-mccann",
+			"https://www.linkedin.com/in/benmccann"
+		]	
+	}, {
+		"@type": "Person",
+		"name": "Simon Holthausen",
+		"image": "https://avatars.githubusercontent.com/u/5968653",
+		"sameAs": [
+			"https://github.com/dummdidumm",
+			"https://twitter.com/dummdidumm_"
+		]	
+	}, {
+		"@type": "Person",
+		"name": "Ignatius Bagus",
+		"url": "https://mauss.dev/",
+		"image": "https://avatars.githubusercontent.com/u/8156777",
+		"sameAs": [
+			"https://twitter.com/alchemauss",
+			"https://github.com/ignatiusmb",
+			"https://gitlab.com/ignatiusmb",
+			"https://www.linkedin.com/in/ignatiusmb"
+		]	
+	}, {
+		"@type": "Person",
+		"name": "Conduitry",
+		"url": "https://conduitry.dev/",
+		"image": "https://avatars.githubusercontent.com/u/16696352",
+		"sameAs": [
+			"https://github.com/Conduitry"
+		]	
+	}, {
+		"@type": "Person",
+		"name": "Dominik Göpel",
+		"url": "https://goepel.it/",
+		"image": "https://avatars.githubusercontent.com/u/611613",
+		"sameAs": [
+			"https://github.com/dominikg",
+			"https://m.webtoo.ls/@dominikg"
+		]	
+	}, {
+		"@type": "Person",
+		"name": "Bjorn Lu",
+		"url": "https://bjornlu.com/",
+		"image": "https://avatars.githubusercontent.com/u/34116392",
+		"sameAs": [
+			"https://github.com/bluwy",
+			"https://twitter.com/bluwyoo",
+			"https://m.webtoo.ls/@bluwy",
+			"https://www.linkedin.com/in/bjorn-lu-3302a31b5",
+			"https://www.reddit.com/user/IamLUG"
+		]	
+	}]
+}


Test patch:
diff --git a/packages/adapter-static/test/test.js b/packages/adapter-static/test/test.js
index 0fe826ace..c4144c1bd 100644
--- a/packages/adapter-static/test/test.js
+++ b/packages/adapter-static/test/test.js
@@ -1,4 +1,4 @@
-import fs from 'fs';
+import fs from 'node:fs';
 import * as assert from 'uvu/assert';
 import { run } from './utils.js';
 
diff --git a/packages/adapter-static/test/utils.js b/packages/adapter-static/test/utils.js
index 1ace81f46..32cd5e3e7 100644
--- a/packages/adapter-static/test/utils.js
+++ b/packages/adapter-static/test/utils.js
@@ -1,7 +1,7 @@
-import { execSync } from 'child_process';
-import fs from 'fs';
-import http from 'http';
-import { fileURLToPath } from 'url';
+import { execSync } from 'node:child_process';
+import fs from 'node:fs';
+import http from 'node:http';
+import { fileURLToPath } from 'node:url';
 import sirv from 'sirv';
 import { chromium, webkit, firefox } from 'playwright';
 import * as uvu from 'uvu';
diff --git a/packages/create-svelte/test/check.js b/packages/create-svelte/test/check.js
index a9852aa8e..56531994c 100644
--- a/packages/create-svelte/test/check.js
+++ b/packages/create-svelte/test/check.js
@@ -1,10 +1,10 @@
-import fs from 'fs';
-import { execSync } from 'child_process';
-import path from 'path';
+import fs from 'node:fs';
+import { execSync } from 'node:child_process';
+import path from 'node:path';
 import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 import { create } from '../index.js';
-import { fileURLToPath } from 'url';
+import { fileURLToPath } from 'node:url';
 import glob from 'tiny-glob';
 // use a directory outside of packages to ensure it isn't added to the pnpm workspace
 const test_workspace_dir = fileURLToPath(
diff --git a/packages/kit/test/apps/amp/vite.config.js b/packages/kit/test/apps/amp/vite.config.js
index 3c59318ad..69200cdb7 100644
--- a/packages/kit/test/apps/amp/vite.config.js
+++ b/packages/kit/test/apps/amp/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js
index a909520da..7a6bc7aff 100644
--- a/packages/kit/test/apps/basics/src/hooks.server.js
+++ b/packages/kit/test/apps/basics/src/hooks.server.js
@@ -1,4 +1,4 @@
-import fs from 'fs';
+import fs from 'node:fs';
 import { sequence } from '@sveltejs/kit/hooks';
 import { HttpError } from '../../../../src/runtime/control';
 import { error, redirect } from '@sveltejs/kit';
@@ -105,6 +105,13 @@ export const handle = sequence(
 			}
 		}
 
+		return resolve(event);
+	},
+	async ({ event, resolve }) => {
+		if (event.url.pathname === '/prerendering/prerendered-endpoint/from-handle-hook') {
+			return event.fetch('/prerendering/prerendered-endpoint/api');
+		}
+
 		return resolve(event);
 	}
 );
diff --git a/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/+page.svelte b/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/+page.svelte
new file mode 100644
index 000000000..146104e14
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/prerendering/prerendered-endpoint/+page.svelte
@@ -0,0 +1,1 @@
+<a href="/prerendering/prerendered-endpoint/from-handle-hook">through handle hook</a>
diff --git a/packages/kit/test/apps/basics/test/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/cross-platform/client.test.js
index 7fe016a14..f9109342f 100644
--- a/packages/kit/test/apps/basics/test/cross-platform/client.test.js
+++ b/packages/kit/test/apps/basics/test/cross-platform/client.test.js
@@ -502,7 +502,7 @@ test.describe('Prefetching', () => {
 		} else {
 			// the preload helper causes an additional request to be made in Firefox,
 			// so we use toBeGreaterThan rather than toBe
-			expect(requests.filter((req) => req.endsWith('.mjs')).length).toBeGreaterThan(0);
+			expect(requests.filter((req) => req.endsWith('.js')).length).toBeGreaterThan(0);
 		}
 
 		expect(requests.includes(`${baseURL}/routing/preloading/preloaded.json`)).toBe(true);
diff --git a/packages/kit/test/apps/basics/test/setup.js b/packages/kit/test/apps/basics/test/setup.js
index 9be580053..dc392bb19 100644
--- a/packages/kit/test/apps/basics/test/setup.js
+++ b/packages/kit/test/apps/basics/test/setup.js
@@ -1,4 +1,4 @@
-import fs from 'fs';
+import fs from 'node:fs';
 
 if (process.platform !== 'win32') {
 	process.chdir('src/routes/routing');
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 05bc19a9f..032825683 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -496,6 +496,22 @@ test.describe('Load', () => {
 			'Im prerendered and called from a non-prerendered +page.server.js'
 		);
 	});
+
+	test('Prerendered +server.js called from a non-prerendered handle hook works', async ({
+		page,
+		javaScriptEnabled
+	}) => {
+		if (javaScriptEnabled) {
+			await page.goto('/prerendering/prerendered-endpoint');
+			await page.click('a', { noWaitAfter: true });
+		} else {
+			await page.goto('/prerendering/prerendered-endpoint/from-handle-hook');
+		}
+
+		expect(await page.textContent('html')).toBe(
+			'{"message":"Im prerendered and called from a non-prerendered +page.server.js"}'
+		);
+	});
 });
 
 test.describe('Nested layouts', () => {
diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js
index 1938615ca..0afea7f14 100644
--- a/packages/kit/test/apps/basics/vite.config.js
+++ b/packages/kit/test/apps/basics/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/apps/dev-only/vite.config.js b/packages/kit/test/apps/dev-only/vite.config.js
index 1938615ca..0afea7f14 100644
--- a/packages/kit/test/apps/dev-only/vite.config.js
+++ b/packages/kit/test/apps/dev-only/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/apps/options-2/test/test.js b/packages/kit/test/apps/options-2/test/test.js
index f9b7cff16..77d6372a3 100644
--- a/packages/kit/test/apps/options-2/test/test.js
+++ b/packages/kit/test/apps/options-2/test/test.js
@@ -32,7 +32,7 @@ test.describe('Service worker', () => {
 		const response = await request.get('/basepath/service-worker.js');
 		const content = await response.text();
 
-		expect(content).toMatch(/\/_app\/immutable\/entry\/start\.[a-z0-9]+\.mjs/);
+		expect(content).toMatch(/\/_app\/immutable\/entry\/start\.[a-z0-9]+\.js/);
 	});
 
 	test('does not register /basepath/service-worker.js', async ({ page }) => {
diff --git a/packages/kit/test/apps/options-2/vite.config.js b/packages/kit/test/apps/options-2/vite.config.js
index 3c59318ad..69200cdb7 100644
--- a/packages/kit/test/apps/options-2/vite.config.js
+++ b/packages/kit/test/apps/options-2/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/apps/options/svelte.config.js b/packages/kit/test/apps/options/svelte.config.js
index b41b487d6..63a9e7710 100644
--- a/packages/kit/test/apps/options/svelte.config.js
+++ b/packages/kit/test/apps/options/svelte.config.js
@@ -24,6 +24,9 @@ const config = {
 		appDir: '_wheee',
 		inlineStyleThreshold: 1024,
 		outDir: '.custom-out-dir',
+		output: {
+			preloadStrategy: 'preload-mjs'
+		},
 		paths: {
 			base: '/path-base',
 			assets: 'https://cdn.example.com/stuff'
diff --git a/packages/kit/test/apps/options/vite.custom.config.js b/packages/kit/test/apps/options/vite.custom.config.js
index 3c59318ad..69200cdb7 100644
--- a/packages/kit/test/apps/options/vite.custom.config.js
+++ b/packages/kit/test/apps/options/vite.custom.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/apps/writes/test/test.js b/packages/kit/test/apps/writes/test/test.js
index 64cbf22c7..397cf0f10 100644
--- a/packages/kit/test/apps/writes/test/test.js
+++ b/packages/kit/test/apps/writes/test/test.js
@@ -1,7 +1,7 @@
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
 import { expect } from '@playwright/test';
-import { fileURLToPath } from 'url';
+import { fileURLToPath } from 'node:url';
 import { test } from '../../../utils.js';
 
 /** @typedef {import('@playwright/test').Response} Response */
diff --git a/packages/kit/test/apps/writes/vite.config.js b/packages/kit/test/apps/writes/vite.config.js
index 3c59318ad..69200cdb7 100644
--- a/packages/kit/test/apps/writes/vite.config.js
+++ b/packages/kit/test/apps/writes/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/vite.config.js b/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/vite.config.js
index eb84383bb..3f12d3677 100644
--- a/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/vite.config.js
+++ b/packages/kit/test/build-errors/apps/prerenderable-incorrect-fragment/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/vite.config.js b/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/vite.config.js
index eb84383bb..3f12d3677 100644
--- a/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/vite.config.js
+++ b/packages/kit/test/build-errors/apps/prerenderable-not-prerendered/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/vite.config.js b/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/vite.config.js
+++ b/packages/kit/test/build-errors/apps/private-dynamic-env-dynamic-import/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/private-dynamic-env/vite.config.js b/packages/kit/test/build-errors/apps/private-dynamic-env/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/private-dynamic-env/vite.config.js
+++ b/packages/kit/test/build-errors/apps/private-dynamic-env/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/private-static-env/vite.config.js b/packages/kit/test/build-errors/apps/private-static-env/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/private-static-env/vite.config.js
+++ b/packages/kit/test/build-errors/apps/private-static-env/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/vite.config.js b/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/vite.config.js
+++ b/packages/kit/test/build-errors/apps/server-only-folder-dynamic-import/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/server-only-folder/vite.config.js b/packages/kit/test/build-errors/apps/server-only-folder/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/server-only-folder/vite.config.js
+++ b/packages/kit/test/build-errors/apps/server-only-folder/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/vite.config.js b/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/vite.config.js
+++ b/packages/kit/test/build-errors/apps/server-only-module-dynamic-import/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/server-only-module/vite.config.js b/packages/kit/test/build-errors/apps/server-only-module/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/server-only-module/vite.config.js
+++ b/packages/kit/test/build-errors/apps/server-only-module/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/build-errors/apps/syntax-error/vite.config.js b/packages/kit/test/build-errors/apps/syntax-error/vite.config.js
index 95a8fe579..713f67a63 100644
--- a/packages/kit/test/build-errors/apps/syntax-error/vite.config.js
+++ b/packages/kit/test/build-errors/apps/syntax-error/vite.config.js
@@ -1,5 +1,5 @@
 import { sveltekit } from '@sveltejs/kit/vite';
-import path from 'path';
+import path from 'node:path';
 
 /** @type {import('vite').UserConfig} */
 const config = {
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js b/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js
index e1494d291..cfa059e22 100644
--- a/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-image/[...slug]/+server.js
@@ -1,4 +1,4 @@
-import * as fs from 'fs';
+import * as fs from 'node:fs';
 
 export const prerender = true;
 
diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js
index 8235e18df..07237ae90 100644
--- a/packages/kit/test/prerendering/basics/test/test.js
+++ b/packages/kit/test/prerendering/basics/test/test.js
@@ -1,5 +1,5 @@
-import * as fs from 'fs';
-import { fileURLToPath } from 'url';
+import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
 import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 
diff --git a/packages/kit/test/prerendering/basics/vite.config.js b/packages/kit/test/prerendering/basics/vite.config.js
index fabe48d16..574b809dd 100644
--- a/packages/kit/test/prerendering/basics/vite.config.js
+++ b/packages/kit/test/prerendering/basics/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/prerendering/options/test/test.js b/packages/kit/test/prerendering/options/test/test.js
index 910fb2a5e..7e8635cc8 100644
--- a/packages/kit/test/prerendering/options/test/test.js
+++ b/packages/kit/test/prerendering/options/test/test.js
@@ -1,5 +1,5 @@
-import * as fs from 'fs';
-import { fileURLToPath } from 'url';
+import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
 import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 
diff --git a/packages/kit/test/prerendering/options/vite.config.js b/packages/kit/test/prerendering/options/vite.config.js
index a2fe148d6..4b90a7655 100644
--- a/packages/kit/test/prerendering/options/vite.config.js
+++ b/packages/kit/test/prerendering/options/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/prerendering/paths-base/test/test.js b/packages/kit/test/prerendering/paths-base/test/test.js
index eea8e571d..92c8dba2d 100644
--- a/packages/kit/test/prerendering/paths-base/test/test.js
+++ b/packages/kit/test/prerendering/paths-base/test/test.js
@@ -1,5 +1,5 @@
-import * as fs from 'fs';
-import { fileURLToPath } from 'url';
+import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
 import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 
diff --git a/packages/kit/test/prerendering/paths-base/vite.config.js b/packages/kit/test/prerendering/paths-base/vite.config.js
index a2fe148d6..4b90a7655 100644
--- a/packages/kit/test/prerendering/paths-base/vite.config.js
+++ b/packages/kit/test/prerendering/paths-base/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/prerendering/ssr-false/test/test.js b/packages/kit/test/prerendering/ssr-false/test/test.js
index 86912a868..26004b364 100644
--- a/packages/kit/test/prerendering/ssr-false/test/test.js
+++ b/packages/kit/test/prerendering/ssr-false/test/test.js
@@ -1,5 +1,5 @@
-import * as fs from 'fs';
-import { fileURLToPath } from 'url';
+import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
 import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 
diff --git a/packages/kit/test/prerendering/ssr-false/vite.config.js b/packages/kit/test/prerendering/ssr-false/vite.config.js
index fabe48d16..574b809dd 100644
--- a/packages/kit/test/prerendering/ssr-false/vite.config.js
+++ b/packages/kit/test/prerendering/ssr-false/vite.config.js
@@ -1,4 +1,4 @@
-import * as path from 'path';
+import * as path from 'node:path';
 import { sveltekit } from '@sveltejs/kit/vite';
 
 /** @type {import('vite').UserConfig} */
diff --git a/packages/kit/test/utils.js b/packages/kit/test/utils.js
index 64e8d68b3..d8b069027 100644
--- a/packages/kit/test/utils.js
+++ b/packages/kit/test/utils.js
@@ -1,8 +1,8 @@
-import fs from 'fs';
-import path from 'path';
-import http from 'http';
+import fs from 'node:fs';
+import path from 'node:path';
+import http from 'node:http';
 import { test as base, devices } from '@playwright/test';
-import { fileURLToPath } from 'url';
+import { fileURLToPath } from 'node:url';
 
 export const test = base.extend({
 	app: async ({ page }, use) => {
diff --git a/packages/package/test/fixtures/resolve-alias/svelte.config.js b/packages/package/test/fixtures/resolve-alias/svelte.config.js
index 7fa9bd72a..2cb56fa23 100644
--- a/packages/package/test/fixtures/resolve-alias/svelte.config.js
+++ b/packages/package/test/fixtures/resolve-alias/svelte.config.js
@@ -1,6 +1,6 @@
 import { vitePreprocess } from '../../../../kit/src/exports/vite/index.js';
-import { fileURLToPath } from 'url';
-import path from 'path';
+import { fileURLToPath } from 'node:url';
+import path from 'node:path';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.join(__filename, '..');
diff --git a/packages/package/test/index.js b/packages/package/test/index.js
index caa98282b..4e05b1442 100644
--- a/packages/package/test/index.js
+++ b/packages/package/test/index.js
@@ -1,6 +1,6 @@
-import fs from 'fs';
-import { join, resolve } from 'path';
-import { fileURLToPath } from 'url';
+import fs from 'node:fs';
+import { join, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
 
 import prettier from 'prettier';
 import { test } from 'uvu';