Repository: sveltejs/kit (js)

Issue:
Svelte Kit's `fetch` and the Browser's `fetch` Produce Inconsistent Results
### Describe the bug

Calling `await fetch(url)` from Svelte Kit `load` produces different results than when calling from the browser.

In a nutshell, given at least one special case, Svelte Kit's `fetch` will be able to retrieve a file, even if the browser's `fetch` will not.

While the special case given in the reproducing repo is not a very common use case, it may still shed some light on other inconsistencies in the custom implementation of `fetch`.

### Reproduction

https://github.com/Acmion/svelte-kit-fetch-bug

### Logs

```shell
(node:1560) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_URL]: Invalid URL: /#bug.html
    at onParseError (internal/url.js:259:9)
    at new URL (internal/url.js:335:5)
    at new Request (file:///D:/Acmion/Temp/svelte-kit-fetch-bug/node_modules/@sveltejs/kit/dist/install-fetch.js:5543:16)
    at file:///D:/Acmion/Temp/svelte-kit-fetch-bug/node_modules/@sveltejs/kit/dist/install-fetch.js:5753:19
    at new Promise (<anonymous>)
    at fetch (file:///D:/Acmion/Temp/svelte-kit-fetch-bug/node_modules/@sveltejs/kit/dist/install-fetch.js:5751:9)
    at loadBrowser (/src/routes/index.svelte:24:24)
    at eval (/src/routes/index.svelte:28:2)
    at Object.$$render (D:\Acmion\Temp\svelte-kit-fetch-bug\node_modules\svelte\internal\index.js:1684:22)
    at Object.default (/D:\Acmion\Temp\svelte-kit-fetch-bug\.svelte-kit\dev/generated/root.svelte:57:127)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:1560) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an a
sync function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process
on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandle
d_rejections_mode). (rejection id: 2)
(node:1560) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are
not handled will terminate the Node.js process with a non-zero exit code.
(node:1560) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_URL]: Invalid URL: /#bug.html
    at onParseError (internal/url.js:259:9)
    at new URL (internal/url.js:335:5)
    at new Request (file:///D:/Acmion/Temp/svelte-kit-fetch-bug/node_modules/@sveltejs/kit/dist/install-fetch.js:5543:16)
    at file:///D:/Acmion/Temp/svelte-kit-fetch-bug/node_modules/@sveltejs/kit/dist/install-fetch.js:5753:19
    at new Promise (<anonymous>)
    at fetch (file:///D:/Acmion/Temp/svelte-kit-fetch-bug/node_modules/@sveltejs/kit/dist/install-fetch.js:5751:9)
    at loadBrowser (/src/routes/index.svelte:24:24)
    at eval (/src/routes/index.svelte:28:2)
    at Object.$$render (D:\Acmion\Temp\svelte-kit-fetch-bug\node_modules\svelte\internal\index.js:1684:22)
    at Object.default (/D:\Acmion\Temp\svelte-kit-fetch-bug\.svelte-kit\dev/generated/root.svelte:57:127)
(node:1560) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an a
sync function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process
on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandle
d_rejections_mode). (rejection id: 4)
```


### System Info

```shell
System:
    OS: Windows 10 10.0.19042
    CPU: (4) x64 Intel(R) Core(TM) i5-2500K CPU @ 3.30GHz
    Memory: 1.09 GB / 7.97 GB
  Binaries:
    Node: 14.16.0 - C:\Program Files\nodejs\node.EXE
    npm: 6.14.11 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.1266.0), Chromium (95.0.1020.53)
    Internet Explorer: 11.0.19041.1202
  npmPackages:
    @sveltejs/kit: next => 1.0.0-next.196
    svelte: ^3.42.6 => 3.44.1
```


### Severity

annoyance

### Additional Information

_No response_

Pull request:
prevent server-side fetch from succeeding if filename contains #
closes #2802

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/breezy-ladybugs-dance.md b/.changeset/breezy-ladybugs-dance.md
new file mode 100644
index 000000000..c15821bb5
--- /dev/null
+++ b/.changeset/breezy-ladybugs-dance.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] change Navigation type to include from/to.params and from/to.routeId
diff --git a/.changeset/green-kiwis-roll.md b/.changeset/green-kiwis-roll.md
new file mode 100644
index 000000000..b5c953e6b
--- /dev/null
+++ b/.changeset/green-kiwis-roll.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] prevent server-side fetch from reading files with # character in the filename
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 57a27c0eb..5f0b7ba75 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -88,6 +88,7 @@
     "breezy-games-count",
     "breezy-grapes-smash",
     "breezy-hairs-greet",
+    "breezy-ladybugs-dance",
     "breezy-mayflies-yawn",
     "breezy-olives-battle",
     "breezy-onions-remember",
@@ -1000,6 +1001,7 @@
     "sour-waves-return",
     "spicy-apples-return",
     "spicy-buckets-draw",
+    "spicy-games-trade",
     "spicy-geese-accept",
     "spicy-glasses-promise",
     "spicy-jeans-rest",
diff --git a/.changeset/spicy-games-trade.md b/.changeset/spicy-games-trade.md
new file mode 100644
index 000000000..bf1225a0a
--- /dev/null
+++ b/.changeset/spicy-games-trade.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Warn about incorrect data-sveltekit-x values
diff --git a/documentation/docs/09-link-options.md b/documentation/docs/09-link-options.md
index b77ab71f7..59ba993b0 100644
--- a/documentation/docs/09-link-options.md
+++ b/documentation/docs/09-link-options.md
@@ -64,3 +64,11 @@ To disable any of these options inside an element where they have been enabled,
 	</div>
 </div>
 ```
+
+To apply an attribute to an element conditionally, do this:
+
+```html
+<div data-sveltekit-reload={shouldReload ? '' : 'off'}>
+```
+
+> This works because in HTML, `<element attribute>` is equivalent to `<element attribute="">`
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index af7d76704..428831bfb 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,13 @@
 # @sveltejs/kit
 
+## 1.0.0-next.466
+
+### Patch Changes
+
+- [breaking] change Navigation type to include from/to.params and from/to.routeId ([#6552](https://github.com/sveltejs/kit/pull/6552))
+
+* Warn about incorrect data-sveltekit-x values ([#6546](https://github.com/sveltejs/kit/pull/6546))
+
 ## 1.0.0-next.465
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index bf0b06ed6..0c0ce69ff 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.465",
+	"version": "1.0.0-next.466",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 1e61ce2cc..f866a04c1 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -145,7 +145,8 @@ export function create_client({ target, base, trailing_slash }) {
 			const url = new URL(location.href);
 
 			invalidating = Promise.resolve().then(async () => {
-				await update(url, []);
+				const intent = get_navigation_intent(url);
+				await update(intent, url, []);
 
 				invalidating = null;
 				force_invalidation = false;
@@ -200,14 +201,13 @@ export function create_client({ target, base, trailing_slash }) {
 
 	/**
 	 * Returns `true` if update completes, `false` if it is aborted
+	 * @param {import('./types').NavigationIntent | undefined} intent
 	 * @param {URL} url
 	 * @param {string[]} redirect_chain
 	 * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
 	 * @param {() => void} [callback]
 	 */
-	async function update(url, redirect_chain, opts, callback) {
-		const intent = get_navigation_intent(url);
-
+	async function update(intent, url, redirect_chain, opts, callback) {
 		const current_token = (token = {});
 		let navigation_result = intent && (await load_route(intent));
 
@@ -399,7 +399,15 @@ export function create_client({ target, base, trailing_slash }) {
 		}
 
 		/** @type {import('types').Navigation} */
-		const navigation = { from: null, to: new URL(location.href), type: 'load' };
+		const navigation = {
+			from: null,
+			to: add_url_properties('to', {
+				params: current.params,
+				routeId: current.route?.id ?? null,
+				url: new URL(location.href)
+			}),
+			type: 'load'
+		};
 		callbacks.after_navigate.forEach((fn) => fn(navigation));
 
 		started = true;
@@ -413,7 +421,7 @@ export function create_client({ target, base, trailing_slash }) {
 	 *   branch: Array<import('./types').BranchNode | undefined>;
 	 *   status: number;
 	 *   error: HttpError | Error | null;
-	 *   routeId: string | null;
+	 *   route: import('types').CSRRoute | null;
 	 *   validation_errors?: Record<string, any> | null;
 	 * }} opts
 	 */
@@ -423,7 +431,7 @@ export function create_client({ target, base, trailing_slash }) {
 		branch,
 		status,
 		error,
-		routeId,
+		route,
 		validation_errors
 	}) {
 		const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
@@ -436,6 +444,7 @@ export function create_client({ target, base, trailing_slash }) {
 				params,
 				branch,
 				error,
+				route,
 				session_id
 			},
 			props: {
@@ -470,7 +479,7 @@ export function create_client({ target, base, trailing_slash }) {
 			result.props.page = {
 				error,
 				params,
-				routeId,
+				routeId: route && route.id,
 				status,
 				url,
 				// The whole page store is updated, but this way the object reference stays the same
@@ -846,7 +855,7 @@ export function create_client({ target, base, trailing_slash }) {
 									branch: branch.slice(0, j + 1).concat(error_loaded),
 									status,
 									error,
-									routeId: route.id
+									route
 								});
 							} catch (e) {
 								continue;
@@ -872,7 +881,7 @@ export function create_client({ target, base, trailing_slash }) {
 			branch,
 			status: 200,
 			error: null,
-			routeId: route.id
+			route
 		});
 	}
 
@@ -941,7 +950,7 @@ export function create_client({ target, base, trailing_slash }) {
 			branch: [root_layout, root_error],
 			status,
 			error,
-			routeId
+			route: null
 		});
 	}
 
@@ -1000,10 +1009,20 @@ export function create_client({ target, base, trailing_slash }) {
 	}) {
 		let should_block = false;
 
+		const intent = get_navigation_intent(url);
+
 		/** @type {import('types').Navigation} */
 		const navigation = {
-			from: current.url,
-			to: url,
+			from: add_url_properties('from', {
+				params: current.params,
+				routeId: current.route?.id ?? null,
+				url: current.url
+			}),
+			to: add_url_properties('to', {
+				params: intent?.params ?? null,
+				routeId: intent?.route.id ?? null,
+				url
+			}),
 			type
 		};
 
@@ -1034,6 +1053,7 @@ export function create_client({ target, base, trailing_slash }) {
 		}
 
 		await update(
+			intent,
 			url,
 			redirect_chain,
 			{
@@ -1153,7 +1173,11 @@ export function create_client({ target, base, trailing_slash }) {
 
 				/** @type {import('types').Navigation & { cancel: () => void }} */
 				const navigation = {
-					from: current.url,
+					from: add_url_properties('from', {
+						params: current.params,
+						routeId: current.route?.id ?? null,
+						url: current.url
+					}),
 					to: null,
 					type: 'unload',
 					cancel: () => (should_block = true)
@@ -1184,7 +1208,7 @@ export function create_client({ target, base, trailing_slash }) {
 			/** @param {Event} event */
 			const trigger_prefetch = (event) => {
 				const { url, options } = find_anchor(event);
-				if (url && options.prefetch === '') {
+				if (url && options.prefetch) {
 					if (is_external_url(url)) return;
 					prefetch(url);
 				}
@@ -1234,7 +1258,7 @@ export function create_client({ target, base, trailing_slash }) {
 				// 2. 'rel' attribute includes external
 				const rel = (a.getAttribute('rel') || '').split(/\s+/);
 
-				if (a.hasAttribute('download') || rel.includes('external') || options.reload === '') {
+				if (a.hasAttribute('download') || rel.includes('external') || options.reload) {
 					return;
 				}
 
@@ -1260,7 +1284,7 @@ export function create_client({ target, base, trailing_slash }) {
 
 				navigate({
 					url,
-					scroll: options.noscroll === '' ? scroll_state() : null,
+					scroll: options.noscroll ? scroll_state() : null,
 					keepfocus: false,
 					redirect_chain: [],
 					details: {
@@ -1379,7 +1403,7 @@ export function create_client({ target, base, trailing_slash }) {
 						  )
 						: original_error,
 					validation_errors,
-					routeId
+					route: routes.find((route) => route.id === routeId) ?? null
 				});
 			} catch (e) {
 				const error = normalize_error(e);
@@ -1434,3 +1458,37 @@ async function load_data(url, invalid) {
 
 	return server_data;
 }
+
+// TODO remove for 1.0
+const properties = [
+	'hash',
+	'href',
+	'host',
+	'hostname',
+	'origin',
+	'pathname',
+	'port',
+	'protocol',
+	'search',
+	'searchParams',
+	'toString',
+	'toJSON'
+];
+
+/**
+ * @param {'from' | 'to'} type
+ * @param {import('types').NavigationTarget} target
+ */
+function add_url_properties(type, target) {
+	for (const prop of properties) {
+		Object.defineProperty(target, prop, {
+			get() {
+				throw new Error(
+					`The navigation shape changed - ${type}.${prop} should now be ${type}.url.${prop}`
+				);
+			}
+		});
+	}
+
+	return target;
+}
diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts
index f2f5a3617..ad59df44a 100644
--- a/packages/kit/src/runtime/client/types.d.ts
+++ b/packages/kit/src/runtime/client/types.d.ts
@@ -81,10 +81,11 @@ export interface DataNode {
 	uses: Uses;
 }
 
-export type NavigationState = {
+export interface NavigationState {
 	branch: Array<BranchNode | undefined>;
 	error: HttpError | Error | null;
 	params: Record<string, string>;
+	route: CSRRoute | null;
 	session_id: number;
 	url: URL;
-};
+}
diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js
index 1f6a659b1..25d183dd3 100644
--- a/packages/kit/src/runtime/client/utils.js
+++ b/packages/kit/src/runtime/client/utils.js
@@ -27,16 +27,14 @@ export function find_anchor(event) {
 	/** @type {HTMLAnchorElement | SVGAElement | undefined} */
 	let a;
 
-	const options = {
-		/** @type {string | null} */
-		noscroll: null,
+	/** @type {boolean | null} */
+	let noscroll = null;
 
-		/** @type {string | null} */
-		prefetch: null,
+	/** @type {boolean | null} */
+	let prefetch = null;
 
-		/** @type {string | null} */
-		reload: null
-	};
+	/** @type {boolean | null} */
+	let reload = null;
 
 	for (const element of event.composedPath()) {
 		if (!(element instanceof Element)) continue;
@@ -46,22 +44,43 @@ export function find_anchor(event) {
 			a = /** @type {HTMLAnchorElement | SVGAElement} */ (element);
 		}
 
-		if (options.noscroll === null) {
-			options.noscroll = element.getAttribute('data-sveltekit-noscroll');
-		}
+		if (noscroll === null) noscroll = get_link_option(element, 'data-sveltekit-noscroll');
+		if (prefetch === null) prefetch = get_link_option(element, 'data-sveltekit-prefetch');
+		if (reload === null) reload = get_link_option(element, 'data-sveltekit-reload');
+	}
 
-		if (options.prefetch === null) {
-			options.prefetch = element.getAttribute('data-sveltekit-prefetch');
-		}
+	const url = a && new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
 
-		if (options.reload === null) {
-			options.reload = element.getAttribute('data-sveltekit-reload');
+	return {
+		a,
+		url,
+		options: {
+			noscroll,
+			prefetch,
+			reload
 		}
-	}
+	};
+}
 
-	const url = a && new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
+const warned = new WeakSet();
+
+/**
+ * @param {Element} element
+ * @param {string} attribute
+ */
+function get_link_option(element, attribute) {
+	const value = element.getAttribute(attribute);
+	if (value === null) return value;
+
+	if (value === '') return true;
+	if (value === 'off') return false;
+
+	if (__SVELTEKIT_DEV__ && !warned.has(element)) {
+		console.error(`Unexpected value for ${attribute} — should be "" or "off"`, element);
+		warned.add(element);
+	}
 
-	return { a, url, options };
+	return false;
 }
 
 /** @param {any} value */
diff --git a/packages/kit/src/runtime/server/page/fetch.js b/packages/kit/src/runtime/server/page/fetch.js
index f67ca95b6..2c5a56466 100644
--- a/packages/kit/src/runtime/server/page/fetch.js
+++ b/packages/kit/src/runtime/server/page/fetch.js
@@ -63,7 +63,7 @@ export function create_fetch({ event, options, state, route, prerender_default }
 			}
 		}
 
-		const resolved = resolve(event.url.pathname, requested.split('?')[0]);
+		const resolved = resolve(event.url.pathname, requested.split('?')[0]).replace(/#.+$/, '');
 
 		/** @type {Response} */
 		let response;
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 7eb746c2d..c09238532 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -219,11 +219,17 @@ export interface LoadEvent<
 	depends: (...deps: string[]) => void;
 }
 
+export interface NavigationTarget {
+	params: Record<string, string> | null;
+	routeId: string | null;
+	url: URL;
+}
+
 export type NavigationType = 'load' | 'unload' | 'link' | 'goto' | 'popstate';
 
 export interface Navigation {
-	from: URL | null;
-	to: URL | null;
+	from: NavigationTarget | null;
+	to: NavigationTarget | null;
 	type: NavigationType;
 	delta?: number;
 }


Test patch:
diff --git a/packages/kit/test/apps/basics/src/hooks.js b/packages/kit/test/apps/basics/src/hooks.js
index b1131b608..6a40df117 100644
--- a/packages/kit/test/apps/basics/src/hooks.js
+++ b/packages/kit/test/apps/basics/src/hooks.js
@@ -37,7 +37,12 @@ export const handle = sequence(
 				? ({ html }) => html.replace('__REPLACEME__', 'Worked!')
 				: undefined
 		});
-		response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');
+
+		try {
+			// in some tests we fetch stuff with undici, and the headers are immutable.
+			// we can safely ignore it in those cases
+			response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');
+		} catch {}
 
 		return response;
 	}
diff --git a/packages/kit/test/apps/basics/src/routes/after-navigate/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/after-navigate/a/+page.svelte
index 36560e6ef..70209b704 100644
--- a/packages/kit/test/apps/basics/src/routes/after-navigate/a/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/after-navigate/a/+page.svelte
@@ -1,10 +1,10 @@
 <script>
 	import { afterNavigate } from '$app/navigation';
 
-	/** @type {URL | null} */
+	/** @type {import('@sveltejs/kit').NavigationTarget | null} */
 	let from;
 
-	/** @type {URL} */
+	/** @type {import('@sveltejs/kit').NavigationTarget} */
 	let to;
 
 	afterNavigate((navigation) => {
@@ -13,5 +13,5 @@
 	});
 </script>
 
-<h1>{from?.pathname} -> {to?.pathname}</h1>
+<h1>{from?.url.pathname} -> {to?.url.pathname}</h1>
 <a href="/after-navigate/b">/b</a>
diff --git a/packages/kit/test/apps/basics/src/routes/after-navigate/b/+page.svelte b/packages/kit/test/apps/basics/src/routes/after-navigate/b/+page.svelte
index c1e43e302..3db6a4022 100644
--- a/packages/kit/test/apps/basics/src/routes/after-navigate/b/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/after-navigate/b/+page.svelte
@@ -1,10 +1,10 @@
 <script>
 	import { afterNavigate } from '$app/navigation';
 
-	/** @type {URL | null} */
+	/** @type {import('@sveltejs/kit').NavigationTarget | null} */
 	let from;
 
-	/** @type {URL} */
+	/** @type {import('@sveltejs/kit').NavigationTarget} */
 	let to;
 
 	afterNavigate((navigation) => {
@@ -13,5 +13,5 @@
 	});
 </script>
 
-<h1>{from?.pathname} -> {to?.pathname}</h1>
+<h1>{from?.url.pathname} -> {to?.url.pathname}</h1>
 <a href="/after-navigate/a">/a</a>
diff --git a/packages/kit/test/apps/basics/src/routes/load/static-file-with-hash/+page.js b/packages/kit/test/apps/basics/src/routes/load/static-file-with-hash/+page.js
new file mode 100644
index 000000000..df92a5908
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/static-file-with-hash/+page.js
@@ -0,0 +1,8 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ fetch }) {
+	const res = await fetch('/load/assets/a#b.txt');
+
+	return {
+		status: res.status
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/static-file-with-hash/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/static-file-with-hash/+page.svelte
new file mode 100644
index 000000000..80f6ef362
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/static-file-with-hash/+page.svelte
@@ -0,0 +1,6 @@
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>status: {data.status}</h1>
diff --git a/packages/kit/test/apps/basics/src/routes/store/navigating/+layout.svelte b/packages/kit/test/apps/basics/src/routes/store/navigating/+layout.svelte
index 706de9b32..c15660dbf 100644
--- a/packages/kit/test/apps/basics/src/routes/store/navigating/+layout.svelte
+++ b/packages/kit/test/apps/basics/src/routes/store/navigating/+layout.svelte
@@ -11,7 +11,7 @@
 <div id="nav-status">
 	{#if $navigating}
 		<p id="navigating">
-			navigating from {$navigating.from.pathname} to {$navigating.to.pathname} ({$navigating.type})
+			navigating from {$navigating.from.url.pathname} to {$navigating.to.url.pathname} ({$navigating.type})
 		</p>
 	{:else}
 		<p id="not-navigating">not currently navigating</p>
diff --git a/packages/kit/test/apps/basics/static/load/assets/a#b.txt b/packages/kit/test/apps/basics/static/load/assets/a#b.txt
new file mode 100644
index 000000000..f86c02590
--- /dev/null
+++ b/packages/kit/test/apps/basics/static/load/assets/a#b.txt
@@ -0,0 +1,1 @@
+nope
\ No newline at end of file
diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js
index 9cf989154..aa4ba7b8b 100644
--- a/packages/kit/test/apps/basics/test/server.test.js
+++ b/packages/kit/test/apps/basics/test/server.test.js
@@ -249,7 +249,12 @@ test.describe('Load', () => {
 		request
 	}) => {
 		const response = await request.get('/errors/error-in-layout');
-		expect(await response.text()).toContain('Error: 500');
+		expect(await response.text()).toContain('Error: 404');
+	});
+
+	test('fetch does not load a file with a # character', async ({ request }) => {
+		const response = await request.get('/load/static-file-with-hash');
+		expect(await response.text()).toContain('status: 404');
 	});
 });