Repository: sveltejs/kit (js)

Issue:
[docs] clarify that unit tests live in `src` and browser tests in `tests`
### Describe the bug

In the SvelteKit Docs, specifically the [Project structure](https://kit.svelte.dev/docs/project-structure) it states that the tests are supposed to be in `/tests`

```
├ tests/
│ └ [your tests]
```
but the config `vite.config.js` points to src 

```
	test: {
		include: ['src/**/*.{test,spec}.{js,ts}']
	}
```

1 of both should be changed ... easy fix but needs a decission on which path too take. Personally I prefer changing vite.config.js to promote a separation of tests from the app source code.

### Reproduction

create a project with 

pnpm create svelte@next 

### Logs

_No response_

### System Info

```Shell
System:
    OS: macOS 13.0.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 595.02 MB / 32.00 GB
    Shell: 5.2.12 - /opt/homebrew/bin/bash
  Binaries:
    Node: 18.12.1 - ~/.nvm/versions/node/v18.12.1/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 8.19.2 - ~/.nvm/versions/node/v18.12.1/bin/npm
  Browsers:
    Chrome: 108.0.5359.98
    Firefox: 107.0.1
    Safari: 16.1
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.90
    @sveltejs/kit: next => 1.0.0-next.581
    svelte: ^3.54.0 => 3.54.0
    vite: ^4.0.0 => 4.0.0
```


### Severity

annoyance
serious, but I can work around it

### Additional Information

does not give a good impression if something is not working as described in the docs

Pull request description:
clarify test file location
* closes #8052
* closes #8066 

Patch:
diff --git a/.changeset/chilly-bobcats-whisper.md b/.changeset/chilly-bobcats-whisper.md
new file mode 100644
index 000000000..da4a6b016
--- /dev/null
+++ b/.changeset/chilly-bobcats-whisper.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] improve split between Vite plugins
diff --git a/.changeset/clever-ducks-wave.md b/.changeset/clever-ducks-wave.md
new file mode 100644
index 000000000..e17730950
--- /dev/null
+++ b/.changeset/clever-ducks-wave.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] set Vite base URL
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 6eef95721..80dfcccaa 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -167,6 +167,7 @@
     "chilled-suns-travel",
     "chilled-years-smash",
     "chilled-zebras-check",
+    "chilly-bobcats-whisper",
     "chilly-cars-battle",
     "chilly-files-greet",
     "chilly-flowers-design",
@@ -182,6 +183,7 @@
     "clean-spiders-sin",
     "clever-dolls-poke",
     "clever-donuts-smile",
+    "clever-ducks-wave",
     "clever-eagles-live",
     "clever-geckos-confess",
     "clever-lizards-grab",
@@ -1505,6 +1507,7 @@
     "unlucky-vans-allow",
     "unlucky-wasps-rest",
     "violet-candles-compare",
+    "violet-donuts-share",
     "violet-goats-wave",
     "violet-jars-give",
     "violet-jars-visit",
diff --git a/documentation/docs/10-getting-started/30-project-structure.md b/documentation/docs/10-getting-started/30-project-structure.md
index ab4bd22b1..09c194070 100644
--- a/documentation/docs/10-getting-started/30-project-structure.md
+++ b/documentation/docs/10-getting-started/30-project-structure.md
@@ -53,13 +53,15 @@ The `src` directory contains the meat of your project.
 
 You can use `.ts` files instead of `.js` files, if using TypeScript.
 
+If you added [Vitest](https://vitest.dev) when you set up your project, your unit tests will live in the `src` directory with a `.test.js` (or `.test.ts`) extension.
+
 ### static
 
 Any static assets that should be served as-is, like `robots.txt` or `favicon.png`, go in here.
 
 ### tests
 
-If you chose to add tests to your project during `npm create svelte@latest`, they will live in this directory.
+If you added [Playwright](https://playwright.dev/) for browser testing when you set up your project, the tests will live in this directory.
 
 ### package.json
 
diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md
index bc23abf68..b200201c1 100644
--- a/documentation/docs/30-advanced/40-service-workers.md
+++ b/documentation/docs/30-advanced/40-service-workers.md
@@ -21,7 +21,7 @@ Inside the service worker you have access to the [`$service-worker` module](/doc
 The following example caches the built app and any files in `static` eagerly, and caches all other requests as they happen. This would make each page work offline once visited.
 
 ```js
-// @ts-nocheck Official TS Service Worker typings are still a work in progress.
+// @errors: 2339
 import { build, files, version } from '$service-worker';
 
 // Create a unique cache name for this deployment
diff --git a/documentation/docs/30-advanced/50-server-only-modules.md b/documentation/docs/30-advanced/50-server-only-modules.md
index 97d7d22b2..c14a4f21d 100644
--- a/documentation/docs/30-advanced/50-server-only-modules.md
+++ b/documentation/docs/30-advanced/50-server-only-modules.md
@@ -26,7 +26,7 @@ export const atlantisCoordinates = [/* redacted */];
 ```
 
 ```js
-// @errors: 2307 7006
+// @errors: 2307 7006 7005
 /// file: src/routes/utils.js
 export { atlantisCoordinates } from '$lib/server/secrets.js';
 
diff --git a/documentation/docs/60-appendix/05-integrations.md b/documentation/docs/60-appendix/05-integrations.md
index 205df56b3..fa3d3b87f 100644
--- a/documentation/docs/60-appendix/05-integrations.md
+++ b/documentation/docs/60-appendix/05-integrations.md
@@ -21,7 +21,9 @@ export default {
 
 ### `svelte-preprocess`
 
-[`svelte-preprocess`](https://github.com/sveltejs/svelte-preprocess) automatically transforms the code in your Svelte templates to provide support for TypeScript, PostCSS, scss/sass, Less, and many other technologies (except CoffeeScript which is [not supported](https://github.com/sveltejs/kit/issues/2920#issuecomment-996469815) by SvelteKit). `vitePreprocess` may be faster and require less configuration, so it is used by default. However, `svelte-preprocess` has some additional functionality not found in `vitePreprocess`. The first step of setting it up is to add `svelte-preprocess` to your [`svelte.config.js`](/docs/configuration). After that, you will often only need to install the corresponding library such as `npm install -D sass` or `npm install -D less`. See the [`svelte-preprocess`](https://github.com/sveltejs/svelte-preprocess) docs for more details.
+`svelte-preprocess` has some additional functionality not found in `vitePreprocess` such as support for Pug, Babel, and global styles. However, `vitePreprocess` may be faster and require less configuration, so it is used by default. Note that CoffeeScript is [not supported](https://github.com/sveltejs/kit/issues/2920#issuecomment-996469815) by SvelteKit.
+
+You will need to install `svelte-preprocess` with `npm install --save-dev svelte-preprocess` and [add it to your `svelte.config.js`](https://github.com/sveltejs/svelte-preprocess/blob/main/docs/usage.md#with-svelte-config). After that, you will often need to [install the corresponding library](https://github.com/sveltejs/svelte-preprocess/blob/main/docs/getting-started.md) such as `npm install -D sass` or `npm install -D less`.
 
 ## Adders
 
diff --git a/documentation/docs/60-appendix/10-migrating.md b/documentation/docs/60-appendix/10-migrating.md
index 298281930..e74a7badc 100644
--- a/documentation/docs/60-appendix/10-migrating.md
+++ b/documentation/docs/60-appendix/10-migrating.md
@@ -160,7 +160,7 @@ declare module 'html-minifier';
 // @filename: index.js
 // ---cut---
 import { minify } from 'html-minifier';
-import { prerendering } from '$app/environment';
+import { building } from '$app/environment';
 
 const minification_options = {
 	collapseBooleanAttributes: true,
@@ -183,16 +183,16 @@ const minification_options = {
 
 /** @type {import('@sveltejs/kit').Handle} */
 export async function handle({ event, resolve }) {
-	const response = await resolve(event);
-
-	if (prerendering && response.headers.get('content-type') === 'text/html') {
-		return new Response(minify(await response.text(), minification_options), {
-			status: response.status,
-			headers: response.headers
-		});
-	}
-
-	return response;
+	let page = '';
+
+	return resolve(event, {
+		transformPageChunk: ({ html, done }) => {
+			html += page;
+			if (done) {
+				return building ? minify(page, minification_options) : page;
+			}
+		}
+	});
 }
 ```
 
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 6140ac3c6..538ae0862 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,15 @@
 # @sveltejs/kit
 
+## 1.0.0-next.582
+
+### Patch Changes
+
+- [fix] improve split between Vite plugins ([#8055](https://github.com/sveltejs/kit/pull/8055))
+
+- [fix] set Vite base URL ([#8046](https://github.com/sveltejs/kit/pull/8046))
+
+- add declaration for vitePreprocess reexport ([#8053](https://github.com/sveltejs/kit/pull/8053))
+
 ## 1.0.0-next.581
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 4a6318bb0..dbabff88a 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.581",
+	"version": "1.0.0-next.582",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
@@ -35,7 +35,7 @@
 		"marked": "^4.2.3",
 		"rollup": "^3.7.0",
 		"svelte": "^3.54.0",
-		"svelte-preprocess": "^4.10.7",
+		"svelte-preprocess": "^5.0.0",
 		"typescript": "^4.9.3",
 		"uvu": "^0.5.6",
 		"vite": "^4.0.0"
diff --git a/packages/kit/src/exports/vite/build/utils.js b/packages/kit/src/exports/vite/build/utils.js
index 1d2a49743..6cab3727d 100644
--- a/packages/kit/src/exports/vite/build/utils.js
+++ b/packages/kit/src/exports/vite/build/utils.js
@@ -108,39 +108,22 @@ export function resolve_symlinks(manifest, file) {
 }
 
 /**
- * The Vite configuration that we use by default.
+ * Partial Vite configuration that we use by default for setting up the build.
+ * Can be used in a non-SvelteKit Vite server and build process such as Storybook.
  * @param {{
  *   config: import('types').ValidatedConfig;
- *   input: Record<string, string>;
  *   ssr: boolean;
- *   outDir: string;
  * }} options
  * @return {import('vite').UserConfig}
  */
-export function get_default_build_config({ config, input, ssr, outDir }) {
+export function get_build_setup_config({ config, ssr }) {
 	const prefix = `${config.kit.appDir}/immutable`;
 
 	return {
-		appType: 'custom',
-		base: ssr ? assets_base(config.kit) : './',
 		build: {
-			cssCodeSplit: true,
 			// don't use the default name to avoid collisions with 'static/manifest.json'
 			manifest: 'vite-manifest.json',
-			outDir,
-			rollupOptions: {
-				input,
-				output: {
-					format: 'esm',
-					entryFileNames: ssr ? '[name].js' : `${prefix}/[name]-[hash].js`,
-					chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name]-[hash].js`,
-					assetFileNames: `${prefix}/assets/[name]-[hash][extname]`,
-					hoistTransitiveImports: false
-				},
-				preserveEntrySignatures: 'strict'
-			},
-			ssr,
-			target: ssr ? 'node14.8' : undefined
+			ssr
 		},
 		define: {
 			__SVELTEKIT_ADAPTER_NAME__: JSON.stringify(config.kit.adapter?.name),
@@ -148,7 +131,6 @@ export function get_default_build_config({ config, input, ssr, outDir }) {
 			__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(config.kit.version.pollInterval),
 			__SVELTEKIT_EMBEDDED__: config.kit.embedded ? 'true' : 'false'
 		},
-		publicDir: ssr ? false : config.kit.files.assets,
 		resolve: {
 			alias: [...get_app_aliases(config.kit), ...get_config_aliases(config.kit)]
 		},
@@ -170,6 +152,57 @@ export function get_default_build_config({ config, input, ssr, outDir }) {
 	};
 }
 
+/**
+ * Partial Vite configuration that we use by default for setting up the build.
+ * Cannot be used in a non-SvelteKit Vite server and build process such as Storybook.
+ * @param {{
+ *   config: import('types').ValidatedConfig;
+ *   input: Record<string, string>;
+ *   ssr: boolean;
+ *   outDir: string;
+ * }} options
+ * @return {import('vite').UserConfig}
+ */
+export function get_build_compile_config({ config, input, ssr, outDir }) {
+	const prefix = `${config.kit.appDir}/immutable`;
+
+	return {
+		appType: 'custom',
+		base: ssr ? assets_base(config.kit) : './',
+		build: {
+			cssCodeSplit: true,
+			outDir,
+			rollupOptions: {
+				input,
+				output: {
+					format: 'esm',
+					entryFileNames: ssr ? '[name].js' : `${prefix}/[name]-[hash].js`,
+					chunkFileNames: ssr ? 'chunks/[name].js' : `${prefix}/chunks/[name]-[hash].js`,
+					assetFileNames: `${prefix}/assets/[name]-[hash][extname]`,
+					hoistTransitiveImports: false
+				},
+				preserveEntrySignatures: 'strict'
+			},
+			target: ssr ? 'node16.14' : undefined
+		},
+		publicDir: ssr ? false : config.kit.files.assets
+	};
+}
+
+/**
+ * The Vite configuration that we use by default for building.
+ * @param {{
+ *   config: import('types').ValidatedConfig;
+ *   input: Record<string, string>;
+ *   ssr: boolean;
+ *   outDir: string;
+ * }} options
+ * @return {import('vite').UserConfig}
+ */
+export function get_default_build_config(options) {
+	return vite.mergeConfig(get_build_setup_config(options), get_build_compile_config(options));
+}
+
 /**
  * @param {import('types').ValidatedKitConfig} config
  * @returns {string}
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index 56e148509..d09d7bea7 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -11,7 +11,7 @@ import { posixify, resolve_entry, to_fs } from '../../../utils/filesystem.js';
 import { load_error_page, load_template } from '../../../core/config/index.js';
 import { SVELTE_KIT_ASSETS } from '../../../constants.js';
 import * as sync from '../../../core/sync/sync.js';
-import { get_mime_lookup, runtime_base, runtime_prefix } from '../../../core/utils.js';
+import { get_mime_lookup, runtime_prefix } from '../../../core/utils.js';
 import { compact } from '../../../utils/array.js';
 import { not_found } from '../utils.js';
 
@@ -284,11 +284,6 @@ export async function dev(vite, vite_config, svelte_config) {
 		}
 	});
 
-	// set `import { version } from '$app/environment'`
-	(await vite.ssrLoadModule(`${runtime_prefix}/env.js`)).set_version(
-		svelte_config.kit.version.name
-	);
-
 	return () => {
 		const serve_static_middleware = vite.middlewares.stack.find(
 			(middleware) =>
@@ -408,13 +403,6 @@ export async function dev(vite, vite_config, svelte_config) {
 					`/${posixify(path.relative(cwd, `${svelte_config.kit.outDir}/generated/root.svelte`))}`
 				);
 
-				const paths = await vite.ssrLoadModule(`${runtime_base}/paths.js`);
-
-				paths.set_paths({
-					base: svelte_config.kit.paths.base,
-					assets
-				});
-
 				let request;
 
 				try {
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 51995ef87..5111eaf06 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -1,24 +1,27 @@
 import { fork } from 'node:child_process';
 import fs from 'node:fs';
 import path from 'node:path';
-import colors from 'kleur';
+import { fileURLToPath } from 'node:url';
+
 import { svelte } from '@sveltejs/vite-plugin-svelte';
+import colors from 'kleur';
 import * as vite from 'vite';
+
 import { mkdirp, posixify, resolve_entry, rimraf } from '../../utils/filesystem.js';
+import { SVELTE_KIT_ASSETS } from '../../constants.js';
+import { create_static_module, create_dynamic_module } from '../../core/env.js';
 import * as sync from '../../core/sync/sync.js';
+import { create_assets } from '../../core/sync/create_manifest_data/index.js';
+import { runtime_base, runtime_directory, runtime_prefix, logger } from '../../core/utils.js';
+import { load_config } from '../../core/config/index.js';
+import { generate_manifest } from '../../core/generate_manifest/index.js';
 import { build_server } from './build/build_server.js';
 import { build_service_worker } from './build/build_service_worker.js';
-import { load_config } from '../../core/config/index.js';
+import { find_deps, get_build_setup_config, get_build_compile_config } from './build/utils.js';
 import { dev } from './dev/index.js';
-import { generate_manifest } from '../../core/generate_manifest/index.js';
-import { runtime_directory, logger } from '../../core/utils.js';
-import { find_deps, get_default_build_config } from './build/utils.js';
+import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
 import { preview } from './preview/index.js';
 import { get_config_aliases, get_app_aliases, get_env } from './utils.js';
-import { fileURLToPath } from 'node:url';
-import { create_static_module, create_dynamic_module } from '../../core/env.js';
-import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
-import { create_assets } from '../../core/sync/create_manifest_data/index.js';
 
 export { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
 
@@ -168,7 +171,7 @@ function kit({ svelte_config }) {
 			}
 		});
 
-		return get_default_build_config({
+		return get_build_compile_config({
 			config: svelte_config,
 			input,
 			ssr: false,
@@ -200,8 +203,8 @@ function kit({ svelte_config }) {
 	check_vite_version();
 
 	/** @type {import('vite').Plugin} */
-	const plugin_build = {
-		name: 'vite-plugin-sveltekit-build',
+	const plugin_setup = {
+		name: 'vite-plugin-sveltekit-setup',
 
 		/**
 		 * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
@@ -226,7 +229,7 @@ function kit({ svelte_config }) {
 			if (is_build) {
 				manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
 
-				const new_config = vite_client_build_config();
+				const new_config = get_build_setup_config({ config: svelte_config, ssr: false });
 
 				const warning = warn_overridden_config(config, new_config);
 				if (warning) console.error(warning + '\n');
@@ -252,20 +255,10 @@ function kit({ svelte_config }) {
 			// dev and preview config can be shared
 			/** @type {import('vite').UserConfig} */
 			const result = {
-				appType: 'custom',
-				base: './',
-				build: {
-					rollupOptions: {
-						// Vite dependency crawler needs an explicit JS entry point
-						// eventhough server otherwise works without it
-						input: `${runtime_directory}/client/start.js`
-					}
-				},
 				define: {
 					__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
 					__SVELTEKIT_EMBEDDED__: svelte_config.kit.embedded ? 'true' : 'false'
 				},
-				publicDir: svelte_config.kit.files.assets,
 				resolve: {
 					alias: [...get_app_aliases(svelte_config.kit), ...get_config_aliases(svelte_config.kit)]
 				},
@@ -554,12 +547,68 @@ function kit({ svelte_config }) {
 				fs.unlinkSync(`${paths.output_dir}/client/${vite_config.build.manifest}`);
 				fs.unlinkSync(`${paths.output_dir}/server/${vite_config.build.manifest}`);
 			}
+		},
+
+		/**
+		 * @see https://vitejs.dev/guide/api-plugin.html#configureserver
+		 */
+		async configureServer(vite) {
+			// set `import { version } from '$app/environment'`
+			(await vite.ssrLoadModule(`${runtime_prefix}/env.js`)).set_version(
+				svelte_config.kit.version.name
+			);
+
+			// set `import { base, assets } from '$app/paths'`
+			const { base, assets } = svelte_config.kit.paths;
+
+			(await vite.ssrLoadModule(`${runtime_base}/paths.js`)).set_paths({
+				base,
+				assets: assets ? SVELTE_KIT_ASSETS : base
+			});
 		}
 	};
 
 	/** @type {import('vite').Plugin} */
-	const plugin_middleware = {
-		name: 'vite-plugin-sveltekit-middleware',
+	const plugin_compile = {
+		name: 'vite-plugin-sveltekit-compile',
+
+		/**
+		 * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
+		 * @see https://vitejs.dev/guide/api-plugin.html#config
+		 */
+		async config(config, config_env) {
+			// The config is created in build_server for SSR mode and passed inline
+			if (config.build?.ssr) return;
+
+			if (config_env.command === 'build') {
+				const new_config = vite_client_build_config();
+
+				const warning = warn_overridden_config(config, new_config);
+				if (warning) console.error(warning + '\n');
+
+				return new_config;
+			}
+
+			// dev and preview config can be shared
+			/** @type {import('vite').UserConfig} */
+			const result = {
+				appType: 'custom',
+				base: svelte_config.kit.paths.base,
+				build: {
+					rollupOptions: {
+						// Vite dependency crawler needs an explicit JS entry point
+						// eventhough server otherwise works without it
+						input: `${runtime_directory}/client/start.js`
+					}
+				},
+				publicDir: svelte_config.kit.files.assets
+			};
+
+			const warning = warn_overridden_config(config, result);
+			if (warning) console.error(warning);
+
+			return result;
+		},
 
 		/**
 		 * Adds the SvelteKit middleware to do SSR in dev mode.
@@ -578,7 +627,7 @@ function kit({ svelte_config }) {
 		}
 	};
 
-	return [plugin_build, plugin_middleware];
+	return [plugin_setup, plugin_compile];
 }
 
 function check_vite_version() {
@@ -634,8 +683,12 @@ function warn_overridden_config(config, resolved_config) {
  * @param {string[]} out used locally to compute the return value
  */
 function find_overridden_config(config, resolved_config, enforced_config, path, out) {
+	if (config == null || resolved_config == null) {
+		return out;
+	}
+
 	for (const key in enforced_config) {
-		if (typeof config === 'object' && config !== null && key in config) {
+		if (typeof config === 'object' && key in config && key in resolved_config) {
 			const enforced = enforced_config[key];
 
 			if (enforced === true) {
@@ -647,7 +700,6 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
 			}
 		}
 	}
-
 	return out;
 }
 
diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js
index 4496c1408..1d040ff31 100644
--- a/packages/kit/src/exports/vite/utils.js
+++ b/packages/kit/src/exports/vite/utils.js
@@ -173,8 +173,12 @@ export function not_found(req, res, base) {
 
 	if (type === 'text/html') {
 		res.setHeader('Content-Type', 'text/html');
-		res.end(`Not found (did you mean <a href="${prefixed}">${prefixed}</a>?)`);
+		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?`
+		);
 	} else {
-		res.end(`Not found (did you mean ${prefixed}?)`);
+		res.end(
+			`The server is configured with a public base URL of /path-base - did you mean to visit ${prefixed} instead?`
+		);
 	}
 }
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 175de0fea..efcc1904d 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -160,8 +160,17 @@ declare module '$app/forms' {
 	 * Usage:
 	 *
 	 * ```js
-	 * const response = await fetch('/form?/action', { method: 'POST', body: formData });
-	 * const result = deserialize(await response.text());
+	 * import { deserialize } from '$app/forms';
+	 *
+	 * async function handleSubmit(event) {
+	 *   const response = await fetch('/form?/action', {
+	 *     method: 'POST',
+	 *     body: new FormData(event.target)
+	 *   });
+	 *
+	 *   const result = deserialize(await response.text());
+	 *   // ...
+	 * }
 	 * ```
 	 */
 	export function deserialize<
@@ -220,6 +229,8 @@ declare module '$app/navigation' {
 	 *
 	 * ```ts
 	 * // Example: Match '/path' regardless of the query parameters
+	 * import { invalidate } from '$app/navigation';
+	 *
 	 * invalidate((url) => url.pathname === '/path');
 	 * ```
 	 * @param url The invalidated URL
@@ -356,6 +367,7 @@ declare module '@sveltejs/kit/hooks' {
 	 * /// file: src/hooks.server.js
 	 * import { sequence } from '@sveltejs/kit/hooks';
 	 *
+	 * /// type: import('@sveltejs/kit').Handle
 	 * async function first({ event, resolve }) {
 	 * 	console.log('first pre-processing');
 	 * 	const result = await resolve(event, {
@@ -369,6 +381,7 @@ declare module '@sveltejs/kit/hooks' {
 	 * 	return result;
 	 * }
 	 *
+	 * /// type: import('@sveltejs/kit').Handle
 	 * async function second({ event, resolve }) {
 	 * 	console.log('second pre-processing');
 	 * 	const result = await resolve(event, {
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 8befad4c5..5fc271531 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -464,8 +464,7 @@ export interface KitConfig {
 		 *         }
 		 *
 		 *         // otherwise fail the build
-		 *           throw new Error(message);
-		 *         }
+		 *         throw new Error(message);
 		 *       }
 		 *     }
 		 *   }
diff --git a/packages/kit/types/private.d.ts b/packages/kit/types/private.d.ts
index 9ea82c8a5..2927959e0 100644
--- a/packages/kit/types/private.d.ts
+++ b/packages/kit/types/private.d.ts
@@ -192,11 +192,12 @@ export interface PrerenderHttpErrorHandler {
 		path: string;
 		referrer: string | null;
 		referenceType: 'linked' | 'fetched';
+		message: string;
 	}): void;
 }
 
 export interface PrerenderMissingIdHandler {
-	(details: { path: string; id: string; referrers: string[] }): void;
+	(details: { path: string; id: string; referrers: string[]; message: string }): void;
 }
 
 export type PrerenderHttpErrorHandlerValue = 'fail' | 'warn' | 'ignore' | PrerenderHttpErrorHandler;
diff --git a/packages/package/package.json b/packages/package/package.json
index cfe1a4bb1..2080e8130 100644
--- a/packages/package/package.json
+++ b/packages/package/package.json
@@ -18,7 +18,7 @@
 	"devDependencies": {
 		"@types/node": "^16.18.6",
 		"svelte": "^3.54.0",
-		"svelte-preprocess": "^4.10.7",
+		"svelte-preprocess": "^5.0.0",
 		"typescript": "^4.9.3",
 		"uvu": "^0.5.6"
 	},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8ecc65ebe..6e9d1b7fe 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -280,7 +280,7 @@ importers:
       set-cookie-parser: ^2.5.1
       sirv: ^2.0.2
       svelte: ^3.54.0
-      svelte-preprocess: ^4.10.7
+      svelte-preprocess: ^5.0.0
       tiny-glob: ^0.2.9
       typescript: ^4.9.3
       undici: 5.14.0
@@ -311,7 +311,7 @@ importers:
       marked: 4.2.3
       rollup: 3.7.0
       svelte: 3.54.0
-      svelte-preprocess: 4.10.7_gf4dcx76vtk2o62ixxeqx7chra
+      svelte-preprocess: 5.0.0_gf4dcx76vtk2o62ixxeqx7chra
       typescript: 4.9.3
       uvu: 0.5.6
       vite: 4.0.0_@types+node@16.18.6
@@ -652,7 +652,7 @@ importers:
       kleur: ^4.1.5
       sade: ^1.8.1
       svelte: ^3.54.0
-      svelte-preprocess: ^4.10.7
+      svelte-preprocess: ^5.0.0
       svelte2tsx: ~0.5.20
       typescript: ^4.9.3
       uvu: ^0.5.6
@@ -664,7 +664,7 @@ importers:
     devDependencies:
       '@types/node': 16.18.6
       svelte: 3.54.0
-      svelte-preprocess: 4.10.7_gf4dcx76vtk2o62ixxeqx7chra
+      svelte-preprocess: 5.0.0_gf4dcx76vtk2o62ixxeqx7chra
       typescript: 4.9.3
       uvu: 0.5.6
 
@@ -4291,6 +4291,54 @@ packages:
       typescript: 4.9.3
     dev: true
 
+  /svelte-preprocess/5.0.0_gf4dcx76vtk2o62ixxeqx7chra:
+    resolution: {integrity: sha512-q7lpa7i2FBu8Pa+G0MmuQQWETBwCKgsGmuq1Sf6n8q4uaG9ZLcLP0Y+etC6bF4sE6EbLxfiI38zV6RfPe3RSfg==}
+    engines: {node: '>= 14.10.0'}
+    requiresBuild: true
+    peerDependencies:
+      '@babel/core': ^7.10.2
+      coffeescript: ^2.5.1
+      less: ^3.11.3 || ^4.0.0
+      postcss: ^7 || ^8
+      postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0
+      pug: ^3.0.0
+      sass: ^1.26.8
+      stylus: ^0.55.0
+      sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0
+      svelte: ^3.23.0
+      typescript: ^3.9.5 || ^4.0.0
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+      coffeescript:
+        optional: true
+      less:
+        optional: true
+      postcss:
+        optional: true
+      postcss-load-config:
+        optional: true
+      pug:
+        optional: true
+      sass:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      typescript:
+        optional: true
+    dependencies:
+      '@types/pug': 2.0.6
+      '@types/sass': 1.43.1
+      detect-indent: 6.1.0
+      magic-string: 0.27.0
+      sorcery: 0.10.0
+      strip-indent: 3.0.0
+      svelte: 3.54.0
+      typescript: 4.9.3
+    dev: true
+
   /svelte/3.54.0:
     resolution: {integrity: sha512-tdrgeJU0hob0ZWAMoKXkhcxXA7dpTg6lZGxUeko5YqvPdJBiyRspGsCwV27kIrbrqPP2WUoSV9ca0gnLlw8YzQ==}
     engines: {node: '>= 8'}
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 4cd03af5b..f93b217f2 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -119,12 +119,28 @@ export async function read_file(file) {
 			} else if (language === 'js' || language === 'ts') {
 				try {
 					const injected = [];
-					if (source.includes('$app/') || source.includes('@sveltejs/kit/')) {
+					if (
+						source.includes('$app/') ||
+						source.includes('$service-worker') ||
+						source.includes('@sveltejs/kit/')
+					) {
 						injected.push(
 							`// @filename: ambient-kit.d.ts`,
 							`/// <reference types="@sveltejs/kit" />`
 						);
 					}
+
+					if (source.includes('$env/')) {
+						// TODO we're hardcoding static env vars that are used in code examples
+						// in the types, which isn't... totally ideal, but will do for now
+						injected.push(
+							`declare module '$env/dynamic/private' { export const env: Record<string, string> }`,
+							`declare module '$env/dynamic/public' { export const env: Record<string, string> }`,
+							`declare module '$env/static/private' { export const API_KEY: string }`,
+							`declare module '$env/static/public' { export const PUBLIC_BASE_URL: string }`
+						);
+					}
+
 					if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) {
 						const params = parse_route_id(options.file || `+page.${language}`)
 							.params.map((param) => `${param.name}: string`)
@@ -142,11 +158,18 @@ export async function read_file(file) {
 							`export type Actions = Kit.Actions<{${params}}>;`
 						);
 					}
-					if (!options.file) {
-						// No named file -> assume that the code is not meant to be type checked
-						// If we don't do this, twoslash would throw errors for e.g. some snippets in `types/ambient.d.ts`
-						injected.push('// @noErrors');
+
+					// special case — we need to make allowances for code snippets coming
+					// from e.g. ambient.d.ts
+					if (file.endsWith('30-modules.md')) {
+						injected.push('// @errors: 7006 7031');
+					}
+
+					// another special case
+					if (source.includes('$lib/types')) {
+						injected.push(`declare module '$lib/types' { export interface User {} }`);
 					}
+
 					if (injected.length) {
 						const injected_str = injected.join('\n');
 						if (source.includes('// @filename:')) {
diff --git a/sites/site-kit/src/lib/styles/text.css b/sites/site-kit/src/lib/styles/text.css
index 0c4de285d..1e62d3cba 100644
--- a/sites/site-kit/src/lib/styles/text.css
+++ b/sites/site-kit/src/lib/styles/text.css
@@ -56,6 +56,14 @@
 	background: transparent;
 }
 
+.text p code {
+	max-width: 100%;
+	display: inline-flex;
+	overflow-x: auto;
+	padding-top: 0;
+	padding-bottom: 0;
+}
+
 .text .code-block .filename {
 	content: attr(data-file);
 	display: block;


Test patch:
diff --git a/packages/kit/test/apps/options/source/template.html b/packages/kit/test/apps/options/source/template.html
index 5b4fa5d89..3a1269063 100644
--- a/packages/kit/test/apps/options/source/template.html
+++ b/packages/kit/test/apps/options/source/template.html
@@ -9,6 +9,6 @@
 	<body>
 		<h1>I am in the template</h1>
 		<div>%sveltekit.body%</div>
-		<a href="/path-base/routing/link-outside-app-target/target">outside app target</a>
+		<a href="/path-base/routing/link-outside-app-target/target/">outside app target</a>
 	</body>
 </html>
diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js
index 516536c6c..588a7cbe5 100644
--- a/packages/kit/test/apps/options/test/test.js
+++ b/packages/kit/test/apps/options/test/test.js
@@ -10,12 +10,14 @@ test.describe('base path', () => {
 		const html = await request.get('/slash/', { headers: { Accept: 'text/html' } });
 		expect(html.status()).toBe(404);
 		expect(await html.text()).toBe(
-			'Not found (did you mean <a href="/path-base/slash/">/path-base/slash/</a>?)'
+			'The server is configured with a public base URL of /path-base - did you mean to visit <a href="/path-base/slash/">/path-base/slash/</a> instead?'
 		);
 
 		const plain = await request.get('/slash/');
 		expect(plain.status()).toBe(404);
-		expect(await plain.text()).toBe('Not found (did you mean /path-base/slash/?)');
+		expect(await plain.text()).toBe(
+			'The server is configured with a public base URL of /path-base - did you mean to visit /path-base/slash/ instead?'
+		);
 	});
 
 	test('serves /', async ({ page, javaScriptEnabled }) => {
@@ -248,9 +250,9 @@ test.describe('Vite options', () => {
 
 test.describe('Routing', () => {
 	test('ignores clicks outside the app target', async ({ page }) => {
-		await page.goto('/path-base/routing/link-outside-app-target/source');
+		await page.goto('/path-base/routing/link-outside-app-target/source/');
 
-		await page.click('[href="/path-base/routing/link-outside-app-target/target"]');
+		await page.click('[href="/path-base/routing/link-outside-app-target/target/"]');
 		await expect(page.locator('h2')).toHaveText('target: 0');
 	});
 });