Repository: sveltejs/kit
Pull request #3576 (2022-01-27 20:39:32)

Exclude routes
For some reason the `gh pr checkout 2932` workflow failed — I can't push to the branch in #2932 — so I've created this branch to continue work on it. Thanks to @tony19 for making this happen. 

For simplicity, and consistency with the `kit.package.files`, `kit.package.exports` and `kit.serviceWorker.files` options, we should only allow a function, rather than an array of strings and regexes and functions. The question is whether to make it a `(filepath: string) => boolean` function, or keep the `basename` argument and either a) introduce inconsistency, or b) impose a breaking change on those other options.

The breaking change would be unfortunate because at the moment it's very easy to use with globbing libraries...

```js
import mm from 'micromatch';

export default {
  kit: {
    package: {
      files: mm.matcher('!**/build.*')
    }
  }
};
```

...so I'm inclined to go with `(filepath: string) => boolean`, since it's trivial to derive the basename.

I also think `kit.excludes` is probably a bit too mysterious. Perhaps `kit.routes` (or `kit.routing.include`, though we don't currently have anything else to go under `routing`) would be better, though at that point we'd have `kit.routes` and `kit.router`, which makes me want to put `kit.router` and `kit.hydrate` under `kit.browser` or something.

### 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
- [ ] 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 `pnpx changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


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

Latest commit: d9e3460e25e8735c03552e13c6e224469d640500

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

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

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

</details>

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

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/sveltejs/kit/new/exclude-routes?filename=.changeset/metal-kings-sing.md&value=---%0A%22%40sveltejs%2Fkit%22%3A%20patch%0A---%0A%0AExclude%20routes%0A)


netlify[bot]: 👷 Deploy Preview for *kit-demo* processing.


🔨 Explore the source changes: d9e3460e25e8735c03552e13c6e224469d640500

🔍 Inspect the deploy log: [https://app.netlify.com/sites/kit-demo/deploys/61f30306d8710a00088fb804](https://app.netlify.com/sites/kit-demo/deploys/61f30306d8710a00088fb804)


Patch:
diff --git a/.changeset/hip-walls-flash.md b/.changeset/hip-walls-flash.md
new file mode 100644
index 000000000..0e7eeb381
--- /dev/null
+++ b/.changeset/hip-walls-flash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Add kit.routes config to customise public/private modules
diff --git a/.changeset/nine-kids-brake.md b/.changeset/nine-kids-brake.md
new file mode 100644
index 000000000..6d4592778
--- /dev/null
+++ b/.changeset/nine-kids-brake.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+Fix typo
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 279b56e65..b21683ac9 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -10,22 +10,10 @@
     "@sveltejs/app-utils": "0.0.18",
     "create-svelte": "2.0.0-alpha.19",
     "@sveltejs/kit": "0.0.30",
-    "@sveltejs/snowpack-config": "0.0.7",
-    "test": "1",
-    "test-basics": "0.0.2",
-    "test-options": "0.0.1",
-    "hn.svelte.dev": "1.0.0",
-    "realworld.svelte.dev": "1.0.0",
-    "sandbox": "1.0.0",
-    "svelte-kit-demo": "1.0.0",
     "@sveltejs/vite-plugin-svelte": "1.0.0-next.0",
     "@sveltejs/adapter-cloudflare-workers": "0.0.1",
-    "set-env-action": "1.0.0",
-    "~TODO~": "0.0.1",
     "@sveltejs/adapter-cloudflare": "1.0.0-next.0",
-    "@sveltejs/adapter-auto": "1.0.0-next",
-    "prerendering-test-basics": "0.0.1",
-    "prerendering-test-options": "0.0.1"
+    "@sveltejs/adapter-auto": "1.0.0-next"
   },
   "changesets": [
     "afraid-eels-tease",
@@ -363,6 +351,7 @@
     "nine-actors-whisper",
     "nine-beans-rhyme",
     "nine-dots-occur",
+    "nine-kids-brake",
     "nine-rats-heal",
     "nine-starfishes-destroy",
     "nine-tomatoes-poke",
@@ -419,6 +408,7 @@
     "pretty-mayflies-tickle",
     "pretty-snakes-burn",
     "proud-bananas-love",
+    "proud-buses-hug",
     "proud-hairs-chew",
     "proud-needles-shake",
     "proud-numbers-promise",
@@ -469,6 +459,7 @@
     "serious-books-push",
     "serious-dolphins-approve",
     "serious-fireants-drum",
+    "serious-horses-rule",
     "serious-vans-arrive",
     "seven-avocados-hug",
     "seven-bags-sniff",
diff --git a/.changeset/proud-buses-hug.md b/.changeset/proud-buses-hug.md
new file mode 100644
index 000000000..98213f1c8
--- /dev/null
+++ b/.changeset/proud-buses-hug.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Decode fetched resources before checking against manifest when prerendering
diff --git a/.github/actions/env/package.json b/.github/actions/env/package.json
index 4541861bc..ebd47ea84 100644
--- a/.github/actions/env/package.json
+++ b/.github/actions/env/package.json
@@ -3,6 +3,7 @@
 	"version": "1.0.0",
 	"description": "Action to set environment",
 	"main": "action/main.js",
+	"private": true,
 	"scripts": {
 		"build": "rollup -c"
 	},
diff --git a/.github/workflows/update_template.yml b/.github/workflows/update_template.yml
index dcca025e1..947221885 100644
--- a/.github/workflows/update_template.yml
+++ b/.github/workflows/update_template.yml
@@ -1,11 +1,9 @@
 name: Update Template
 
 on:
-  workflow_run:
-    workflows: [Release]
-    types:
-      - completed
-  workflow_dispatch:
+  push:
+    tags:
+      - create-svelte@*
 
 jobs:
   release:
@@ -51,9 +49,8 @@ jobs:
       - name: Publish
         working-directory: ${{ github.workspace }}/template
         run: |
-          touch "generated-$(date +%s)"
           git config user.email "noreply@svelte.dev"
           git config user.name "[bot]"
           git add -A
-          git commit -m "version $npm_package_version"
+          git commit -m "create-svelte version ${{ github.ref_name }}"
           git push -u origin main -f
diff --git a/documentation/docs/01-routing.md b/documentation/docs/01-routing.md
index 344b71c2e..8af291a24 100644
--- a/documentation/docs/01-routing.md
+++ b/documentation/docs/01-routing.md
@@ -175,7 +175,7 @@ export default {
 
 ### Private modules
 
-A filename that has a segment with a leading underscore, such as `src/routes/foo/_Private.svelte` or `src/routes/bar/_utils/cool-util.js`, is hidden from the router, but can be imported by files that are not.
+Files and directories with a leading `_` or `.` (other than [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI)) are private by default, meaning that they do not create routes (but can be imported by files that do). You can configure which modules are considered public or private with the [`routes`](#configuration-routes) configuration.
 
 ### Advanced
 
diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md
index 95e3b2f42..9b144f2c8 100644
--- a/documentation/docs/14-configuration.md
+++ b/documentation/docs/14-configuration.md
@@ -58,6 +58,7 @@ const config = {
 			onError: 'fail'
 		},
 		router: true,
+		routes: (filepath) => !/(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/.test(filepath),
 		serviceWorker: {
 			register: true,
 			files: (filepath) => !/\.DS_STORE/.test(filepath)
@@ -224,6 +225,10 @@ See [Prerendering](#page-options-prerender). An object containing zero or more o
 
 Enables or disables the client-side [router](#page-options-router) app-wide.
 
+### routes
+
+A `(filepath: string) => boolean` function that determines which files create routes and which are treated as [private modules](#routing-private-modules).
+
 ### serviceWorker
 
 An object containing zero or more of the following values:
diff --git a/documentation/faq/70-packages.md b/documentation/faq/70-packages.md
index 9281f3e62..a50213276 100644
--- a/documentation/faq/70-packages.md
+++ b/documentation/faq/70-packages.md
@@ -2,10 +2,14 @@
 question: How do I fix the error I'm getting trying to include a package?
 ---
 
-SSR in Vite is not yet stable. Libraries work best with Vite when they distribute both CJS and ESM in their package and you may wish to work with library authors to make this happen.
+Vite's SSR support has become fairly stable since Vite 2.7. Most issues related to including a library are due to incorrect packaging.
 
-Svelte components must be written entirely in ESM. It is encouraged to make sure the dependencies of external Svelte components provide an ESM version. However, in order to handle CJS dependencies [`vite-plugin-svelte` will look for any CJS dependencies of external Svelte components](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies) and ask Vite to pre-bundle them by automatically adding them to Vite's `optimizeDeps.include` which will use `esbuild` to convert them to ESM.
+Libraries work best with Vite when they distribute an ESM version and you may wish to suggest this to library authors. Here are a few things to keep in mind when checking if a library is packaged correctly:
+- `exports` takes precedence over the other entry point fields such as `main` and `module`. Adding an `exports` field may not be backwards-compatible as it prevents deep imports.
+- ESM files should end with `.mjs` unless `"type": "module"` is set in which any case CommonJS files should end with `.cjs`.
+- `main` should be defined if `exports` is not. It should be either a CommonJS or ESM file and adhere to the previous bullet. If a `module` field is defined, it should refer to an ESM file.
+- Svelte components should be distributed entirely as ESM and have a `svelte` field defining the entry point.
 
-A side effect of the approach above is that it takes longer to load the initial page. If this becomes noticable, try setting [experimental.prebundleSvelteLibraries: true](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#prebundlesveltelibraries) in `svelte.config.js`. Note that this option is experimental.
+It is encouraged to make sure the dependencies of external Svelte components provide an ESM version. However, in order to handle CommonJS dependencies [`vite-plugin-svelte` will look for any CJS dependencies of external Svelte components](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies) and ask Vite to pre-bundle them by automatically adding them to Vite's `optimizeDeps.include` which will use `esbuild` to convert them to ESM. A side effect of this approach is that it takes longer to load the initial page. If this becomes noticable, try setting [experimental.prebundleSvelteLibraries: true](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#prebundlesveltelibraries) in `svelte.config.js`. Note that this option is experimental.
 
 If you are still encountering issues we recommend checking [the list of known Vite issues most commonly affecting SvelteKit users](https://github.com/sveltejs/kit/issues/2086) and searching both [the Vite issue tracker](https://github.com/vitejs/vite/issues) and the issue tracker of the library in question. Sometimes issues can be worked around by fiddling with the [`optimizeDeps`](https://vitejs.dev/config/#dep-optimization-options) or [`ssr`](https://vitejs.dev/config/#ssr-options) config values.
diff --git a/documentation/migrating/04-pages.md b/documentation/migrating/04-pages.md
index f06db769e..8986d513f 100644
--- a/documentation/migrating/04-pages.md
+++ b/documentation/migrating/04-pages.md
@@ -4,7 +4,7 @@ title: Pages and layouts
 
 ### Renamed files
 
-Your custom error page component should be renamed from `_error.svelte` to `__error.svelte`. Any `_layout.svelte` files should likewise be renamed `__layout.svelte`. The double underscore prefix is reserved for SvelteKit; your own private modules are still denoted with a single `_` prefix.
+Your custom error page component should be renamed from `_error.svelte` to `__error.svelte`. Any `_layout.svelte` files should likewise be renamed `__layout.svelte`. The double underscore prefix is reserved for SvelteKit; your own [private modules](#routing-private-modules) are still denoted with a single `_` prefix (configurable via [`routes`](docs#configuration-routes) config).
 
 ### Imports
 
diff --git a/package.json b/package.json
index 8abc7d377..03fc2fa30 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,8 @@
 		"check": "pnpm -r check",
 		"lint": "pnpm -r lint",
 		"format": "pnpm -r format",
-		"release": "pnpm publish --tag=next --filter=\"@sveltejs/*\" --filter=\"create-svelte\"",
-		"preview:docs": "action-deploy-docs --project=kit",
-		"changeset:version": "changeset version && pnpm i --lockfile-only"
+		"release": "changeset publish",
+		"preview:docs": "action-deploy-docs --project=kit"
 	},
 	"repository": {
 		"type": "git",
diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md
index 15f26d6f5..8f75a731f 100644
--- a/packages/create-svelte/CHANGELOG.md
+++ b/packages/create-svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
 # create-svelte
 
+## 2.0.0-next.102
+
+### Patch Changes
+
+- Fix typo ([#3583](https://github.com/sveltejs/kit/pull/3583))
+
 ## 2.0.0-next.101
 
 ### Patch Changes
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index fd4471339..7dc2a2006 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "create-svelte",
-	"version": "2.0.0-next.101",
+	"version": "2.0.0-next.102",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/create-svelte/shared/README.md b/packages/create-svelte/shared/README.md
index 82510ca06..2b4623576 100644
--- a/packages/create-svelte/shared/README.md
+++ b/packages/create-svelte/shared/README.md
@@ -1,6 +1,6 @@
 # create-svelte
 
-Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
+Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
 
 ## Creating a project
 
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 52de0d8ec..07eceecde 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,13 @@
 # @sveltejs/kit
 
+## 1.0.0-next.248
+
+### Patch Changes
+
+- Decode fetched resources before checking against manifest when prerendering ([#3571](https://github.com/sveltejs/kit/pull/3571))
+
+* Breaking: remove -H and (conflicting) -h shortcuts from CLI ([#3573](https://github.com/sveltejs/kit/pull/3573))
+
 ## 1.0.0-next.247
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 59855a0d9..7329bd881 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.247",
+	"version": "1.0.0-next.248",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js
index bda10e275..06b8c426c 100644
--- a/packages/kit/src/core/config/options.js
+++ b/packages/kit/src/core/config/options.js
@@ -243,6 +243,8 @@ const options = object(
 
 			router: boolean(true),
 
+			routes: fun((filepath) => !/(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/.test(filepath)),
+
 			serviceWorker: object({
 				register: boolean(true),
 				files: fun((filename) => !/\.DS_STORE/.test(filename))
diff --git a/packages/kit/src/core/create_manifest_data/index.js b/packages/kit/src/core/create_manifest_data/index.js
index 12eafda7d..273617842 100644
--- a/packages/kit/src/core/create_manifest_data/index.js
+++ b/packages/kit/src/core/create_manifest_data/index.js
@@ -87,17 +87,16 @@ export default function create_manifest_data({
 				}
 			});
 
-			if (name[0] === '_') {
-				if (name[1] === '_' && !specials.has(name)) {
-					throw new Error(`Files and directories prefixed with __ are reserved (saw ${file})`);
-				}
-
-				return;
+			if (basename.startsWith('__') && !specials.has(name)) {
+				throw new Error(`Files and directories prefixed with __ are reserved (saw ${file})`);
 			}
 
-			if (basename[0] === '.' && basename !== '.well-known') return null;
 			if (!is_dir && !/^(\.[a-z0-9]+)+$/i.test(ext)) return null; // filter out tmp files etc
 
+			if (!config.kit.routes(file)) {
+				return;
+			}
+
 			const segment = is_dir ? basename : name;
 
 			if (/\]\[/.test(segment)) {
diff --git a/packages/kit/src/core/create_manifest_data/index.spec.js b/packages/kit/src/core/create_manifest_data/index.spec.js
index 349237633..dc1a81cc3 100644
--- a/packages/kit/src/core/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/create_manifest_data/index.spec.js
@@ -3,29 +3,20 @@ import { fileURLToPath } from 'url';
 import { test } from 'uvu';
 import * as assert from 'uvu/assert';
 import create_manifest_data from './index.js';
+import options from '../config/options.js';
 
 const cwd = fileURLToPath(new URL('./test', import.meta.url));
 
 /**
  * @param {string} dir
- * @param {string[]} [extensions]
+ * @param {import('types/config').Config} config
  * @returns
  */
-const create = (dir, extensions = ['.svelte']) => {
-	/** @type {import('types/config').Config} */
-	const initial = {
-		extensions,
-		kit: {
-			files: {
-				assets: path.resolve(cwd, 'static'),
-				routes: path.resolve(cwd, dir)
-			},
-			appDir: '_app',
-			serviceWorker: {
-				files: (filepath) => !/\.DS_STORE/.test(filepath)
-			}
-		}
-	};
+const create = (dir, config = {}) => {
+	const initial = options(config, 'config');
+
+	initial.kit.files.assets = path.resolve(cwd, 'static');
+	initial.kit.files.routes = path.resolve(cwd, dir);
 
 	return create_manifest_data({
 		config: /** @type {import('types/config').ValidatedConfig} */ (initial),
@@ -265,6 +256,83 @@ test('ignores files and directories with leading dots except .well-known', () =>
 	]);
 });
 
+test('ignores files by `kit.excludes` config w/RegExp', () => {
+	const { routes } = create('samples/hidden-by-excludes-config', {
+		kit: {
+			routes: (filepath) => !filepath.endsWith('.test.js') && !filepath.endsWith('.spec.js')
+		}
+	});
+
+	assert.equal(
+		routes
+			.map((r) => r.type === 'endpoint' && r.file)
+			.filter(Boolean)
+			.sort(),
+		[
+			'samples/hidden-by-excludes-config/.a.js',
+			'samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js',
+			'samples/hidden-by-excludes-config/_a.js',
+			'samples/hidden-by-excludes-config/a.js',
+			'samples/hidden-by-excludes-config/a.md',
+			'samples/hidden-by-excludes-config/subdir/.a.js',
+			'samples/hidden-by-excludes-config/subdir/_a.js',
+			'samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js',
+			'samples/hidden-by-excludes-config/subdir/a.js',
+			'samples/hidden-by-excludes-config/subdir/a.md'
+		].sort()
+	);
+});
+
+test('ignores files by `kit.excludes` config w/string', () => {
+	const { routes } = create('samples/hidden-by-excludes-config', {
+		kit: {
+			routes: (filepath) => !filepath.split('/').includes('a.js')
+		}
+	});
+
+	assert.equal(
+		routes
+			.map((r) => r.type === 'endpoint' && r.file)
+			.filter(Boolean)
+			.sort(),
+		[
+			'samples/hidden-by-excludes-config/.a.js',
+			'samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js',
+			'samples/hidden-by-excludes-config/_a.js',
+			'samples/hidden-by-excludes-config/a.md',
+			'samples/hidden-by-excludes-config/a.spec.js',
+			'samples/hidden-by-excludes-config/subdir/.a.js',
+			'samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js',
+			'samples/hidden-by-excludes-config/subdir/_a.js',
+			'samples/hidden-by-excludes-config/subdir/a.md',
+			'samples/hidden-by-excludes-config/subdir/a.spec.js'
+		].sort()
+	);
+});
+
+test('ignores files by `kit.excludes` config w/function', () => {
+	const { routes } = create('samples/hidden-by-excludes-config', {
+		kit: {
+			routes: (filepath) => !filepath.startsWith('samples/hidden-by-excludes-config/subdir')
+		}
+	});
+
+	assert.equal(
+		routes
+			.map((r) => r.type === 'endpoint' && r.file)
+			.filter(Boolean)
+			.sort(),
+		[
+			'samples/hidden-by-excludes-config/.a.js',
+			'samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js',
+			'samples/hidden-by-excludes-config/_a.js',
+			'samples/hidden-by-excludes-config/a.js',
+			'samples/hidden-by-excludes-config/a.md',
+			'samples/hidden-by-excludes-config/a.spec.js'
+		].sort()
+	);
+});
+
 test('allows multiple slugs', () => {
 	const { routes } = create('samples/multiple-slugs');
 
@@ -303,12 +371,9 @@ test('ignores things that look like lockfiles', () => {
 });
 
 test('works with custom extensions', () => {
-	const { components, routes } = create('samples/custom-extension', [
-		'.jazz',
-		'.beebop',
-		'.funk',
-		'.svelte'
-	]);
+	const { components, routes } = create('samples/custom-extension', {
+		extensions: ['.jazz', '.beebop', '.funk', '.svelte']
+	});
 
 	const index = 'samples/custom-extension/index.funk';
 	const about = 'samples/custom-extension/about.jazz';
diff --git a/packages/kit/src/runtime/server/page/load_node.js b/packages/kit/src/runtime/server/page/load_node.js
index 66a61a9ff..bb878ae14 100644
--- a/packages/kit/src/runtime/server/page/load_node.js
+++ b/packages/kit/src/runtime/server/page/load_node.js
@@ -105,7 +105,7 @@ export async function load_node({
 				// handle fetch requests for static assets. e.g. prebaked data, etc.
 				// we need to support everything the browser's fetch supports
 				const prefix = options.paths.assets || options.paths.base;
-				const filename = (
+				const filename = decodeURIComponent(
 					resolved.startsWith(prefix) ? resolved.slice(prefix.length) : resolved
 				).slice(1);
 				const filename_html = `${filename}/index.html`; // path may also match path/index.html
diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts
index 4fadf2ee4..19b86cd05 100644
--- a/packages/kit/types/config.d.ts
+++ b/packages/kit/types/config.d.ts
@@ -155,6 +155,7 @@ export interface Config {
 			onError?: PrerenderOnErrorValue;
 		};
 		router?: boolean;
+		routes?: (filepath: string) => boolean;
 		serviceWorker?: {
 			register?: boolean;
 			files?: (filepath: string) => boolean;


Test patch:
diff --git a/packages/adapter-static/test/apps/prerendered/package.json b/packages/adapter-static/test/apps/prerendered/package.json
index 71b8a3463..364ccc8cd 100644
--- a/packages/adapter-static/test/apps/prerendered/package.json
+++ b/packages/adapter-static/test/apps/prerendered/package.json
@@ -1,6 +1,7 @@
 {
 	"name": "~TODO~",
 	"version": "0.0.1",
+	"private": true,
 	"scripts": {
 		"dev": "../../../../kit/svelte-kit.js dev",
 		"build": "../../../../kit/svelte-kit.js build",
diff --git a/packages/adapter-static/test/apps/spa/package.json b/packages/adapter-static/test/apps/spa/package.json
index aa14bafe5..df8d3986e 100644
--- a/packages/adapter-static/test/apps/spa/package.json
+++ b/packages/adapter-static/test/apps/spa/package.json
@@ -1,6 +1,7 @@
 {
 	"name": "~TODO~",
 	"version": "0.0.1",
+	"private": true,
 	"scripts": {
 		"dev": "../../../../kit/svelte-kit.js dev",
 		"build": "../../../../kit/svelte-kit.js build",
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__error.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__error.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__layout.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__layout.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/_a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/_a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.md b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.spec.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.spec.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__error.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__error.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__layout.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__layout.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/_a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/_a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.md b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.spec.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.spec.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/packaging/test/fixtures/emitTypes-false/expected/package.json b/packages/kit/src/packaging/test/fixtures/emitTypes-false/expected/package.json
index 3c97b0b19..c4ccc94b1 100644
--- a/packages/kit/src/packaging/test/fixtures/emitTypes-false/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/emitTypes-false/expected/package.json
@@ -11,4 +11,4 @@
 		".": "./index.js"
 	},
 	"svelte": "./index.js"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/src/packaging/test/fixtures/exports-merge/expected/package.json b/packages/kit/src/packaging/test/fixtures/exports-merge/expected/package.json
index 87ee8e1ea..085f6ac97 100644
--- a/packages/kit/src/packaging/test/fixtures/exports-merge/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/exports-merge/expected/package.json
@@ -19,4 +19,4 @@
 	},
 	"type": "module",
 	"svelte": "./index.js"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/src/packaging/test/fixtures/exports-replace/expected/package.json b/packages/kit/src/packaging/test/fixtures/exports-replace/expected/package.json
index e84633d71..45ec663ff 100644
--- a/packages/kit/src/packaging/test/fixtures/exports-replace/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/exports-replace/expected/package.json
@@ -11,4 +11,4 @@
 		"./package.json": "./package.json"
 	},
 	"svelte": "./Test.svelte"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/src/packaging/test/fixtures/files-exclude/expected/package.json b/packages/kit/src/packaging/test/fixtures/files-exclude/expected/package.json
index 26f20bb77..2303af531 100644
--- a/packages/kit/src/packaging/test/fixtures/files-exclude/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/files-exclude/expected/package.json
@@ -11,4 +11,4 @@
 		".": "./index.js"
 	},
 	"svelte": "./index.js"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/src/packaging/test/fixtures/javascript/expected/package.json b/packages/kit/src/packaging/test/fixtures/javascript/expected/package.json
index 3d7d65e37..bbabe850c 100644
--- a/packages/kit/src/packaging/test/fixtures/javascript/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/javascript/expected/package.json
@@ -14,4 +14,4 @@
 		".": "./index.js"
 	},
 	"svelte": "./index.js"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/src/packaging/test/fixtures/resolve-alias/expected/package.json b/packages/kit/src/packaging/test/fixtures/resolve-alias/expected/package.json
index 3e59313a2..bb3d7d208 100644
--- a/packages/kit/src/packaging/test/fixtures/resolve-alias/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/resolve-alias/expected/package.json
@@ -13,4 +13,4 @@
 		"./sub/foo": "./sub/foo.js"
 	},
 	"svelte": "./index.js"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/src/packaging/test/fixtures/typescript/expected/package.json b/packages/kit/src/packaging/test/fixtures/typescript/expected/package.json
index 59ea2b06d..91b6a3b5b 100644
--- a/packages/kit/src/packaging/test/fixtures/typescript/expected/package.json
+++ b/packages/kit/src/packaging/test/fixtures/typescript/expected/package.json
@@ -13,4 +13,4 @@
 		".": "./index.js"
 	},
 	"svelte": "./index.js"
-}
+}
\ No newline at end of file
diff --git a/packages/kit/test/prerendering/basics/src/routes/load-file-with-spaces.svelte b/packages/kit/test/prerendering/basics/src/routes/load-file-with-spaces.svelte
new file mode 100644
index 000000000..b0a3d165f
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/load-file-with-spaces.svelte
@@ -0,0 +1,25 @@
+<script context="module">
+	/** @type {import('@sveltejs/kit').Load} */
+	export async function load({ fetch }) {
+		const r1 = await fetch('/file%20with%20spaces.json');
+		const p1 = await r1.json();
+
+		const r2 = await fetch('/file with spaces.json');
+		const p2 = await r2.json();
+
+		if (p1.answer !== p2.answer) {
+			throw new Error('oops');
+		}
+
+		return {
+			props: p1
+		};
+	}
+</script>
+
+<script>
+	/** @type {number} */
+	export let answer;
+</script>
+
+<h1>answer: {answer}</h1>
diff --git a/packages/kit/test/prerendering/basics/static/file with spaces.json b/packages/kit/test/prerendering/basics/static/file with spaces.json
new file mode 100644
index 000000000..6e887fd6a
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/static/file with spaces.json
@@ -0,0 +1,1 @@
+{ "answer": 42 }
diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js
index 16972c334..2cef10ee3 100644
--- a/packages/kit/test/prerendering/basics/test/test.js
+++ b/packages/kit/test/prerendering/basics/test/test.js
@@ -58,4 +58,9 @@ test('renders page with unbuffered data from endpoint', () => {
 	assert.equal(json, JSON.stringify({ answer: 42 }));
 });
 
+test('loads a file with spaces in the filename', () => {
+	const content = read('load-file-with-spaces/index.html');
+	assert.ok(content.includes('<h1>answer: 42</h1>'), content);
+});
+
 test.run();