Repository: sveltejs/kit
Pull request #7613 (2022-11-11 14:07:50)

[docs] clarify load/action connection in example
From a Discord question

### 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 `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 2f8fada07a19f453cbdbc1b92f6160019b1d74a3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/sveltejs/kit/new/actions-load-clarification?filename=.changeset/sixty-phones-clean.md&value=---%0A%0A---%0A%0A%5Bdocs%5D%20clarify%20load%2Faction%20connection%20in%20example%0A)



Patch:
diff --git a/.changeset/fair-panthers-notice.md b/.changeset/fair-panthers-notice.md
new file mode 100644
index 000000000..12311421d
--- /dev/null
+++ b/.changeset/fair-panthers-notice.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+allow async function for `enhance` action parameter
diff --git a/.changeset/kind-dragons-poke.md b/.changeset/kind-dragons-poke.md
new file mode 100644
index 000000000..4407bb466
--- /dev/null
+++ b/.changeset/kind-dragons-poke.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Run service worker during development
diff --git a/.changeset/pink-jokes-call.md b/.changeset/pink-jokes-call.md
new file mode 100644
index 000000000..4f930b9d5
--- /dev/null
+++ b/.changeset/pink-jokes-call.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[feat] support throwing redirect in handle
diff --git a/.changeset/real-onions-pull.md b/.changeset/real-onions-pull.md
new file mode 100644
index 000000000..4b1873697
--- /dev/null
+++ b/.changeset/real-onions-pull.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Workaround for the DOM clobbering for use:enhance
diff --git a/.changeset/serious-buttons-push.md b/.changeset/serious-buttons-push.md
new file mode 100644
index 000000000..b10699320
--- /dev/null
+++ b/.changeset/serious-buttons-push.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] don't cache prefetch errors
diff --git a/documentation/docs/20-core-concepts/30-form-actions.md b/documentation/docs/20-core-concepts/30-form-actions.md
index 98a30ffe6..d204ab26e 100644
--- a/documentation/docs/20-core-concepts/30-form-actions.md
+++ b/documentation/docs/20-core-concepts/30-form-actions.md
@@ -97,6 +97,12 @@ Each action receives a `RequestEvent` object, allowing you to read the data with
 ```js
 // @errors: 2339 2304
 /// file: src/routes/login/+page.server.js
+/** @type {import('./$types').PageServerLoad} */
+export async function load({ cookies }) {
+	const user = await db.getUserFromSession(cookies.get('sessionid'));
+	return { user };
+}
+
 /** @type {import('./$types').Actions} */
 export const actions = {
 	login: async ({ cookies, request }) => {
diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md
index ee26eb59e..0af6e9598 100644
--- a/documentation/docs/30-advanced/40-service-workers.md
+++ b/documentation/docs/30-advanced/40-service-workers.md
@@ -10,4 +10,12 @@ In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker
 
 Inside the service worker you have access to the [`$service-worker` module](/docs/modules#$service-worker). If your Vite config specifies `define`, this will be applied to service workers as well as your server/client builds.
 
-Because it needs to be bundled (since browsers don't yet support `import` in this context), and depends on the client-side app's build manifest, **service workers only work in the production build, not in development**. To test it locally, use `vite preview`.
+The service worker is bundled for production, but not during development. For that reason, only browsers that support [modules in service workers](https://web.dev/es-modules-in-sw) will be able to use them at dev time. If you are manually registering your service worker, you will need to pass the `{ type: 'module' }` option in development:
+
+```js
+import { dev } from '$app/environment';
+
+navigator.serviceWorker.register('/service-worker.js', {
+	type: dev ? 'module' : 'classic'
+});
+```
\ No newline at end of file
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 37b74741e..72ee0afc4 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -35,7 +35,7 @@ export default function create_manifest_data({
 /**
  * @param {import('types').ValidatedConfig} config
  */
-function create_assets(config) {
+export function create_assets(config) {
 	return list_files(config.kit.files.assets).map((file) => ({
 		file,
 		size: fs.statSync(path.resolve(config.kit.files.assets, file)).size,
diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js
index 89affc8d9..82567f41f 100644
--- a/packages/kit/src/core/utils.js
+++ b/packages/kit/src/core/utils.js
@@ -1,7 +1,7 @@
 import path from 'path';
 import colors from 'kleur';
 import { fileURLToPath } from 'url';
-import { posixify } from '../utils/filesystem.js';
+import { posixify, to_fs } from '../utils/filesystem.js';
 
 /**
  * Resolved path of the `runtime` directory
@@ -23,10 +23,7 @@ export const runtime_prefix = posixify_path(runtime_directory);
  */
 export const runtime_base = runtime_directory.startsWith(process.cwd())
 	? `/${path.relative('.', runtime_directory)}`
-	: `/@fs${
-			// Windows/Linux separation - Windows starts with a drive letter, we need a / in front there
-			runtime_directory.startsWith('/') ? '' : '/'
-	  }${runtime_directory}`;
+	: to_fs(runtime_directory);
 
 /** @param {string} str */
 function posixify_path(str) {
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index a00afc67c..541547f6d 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -6,7 +6,7 @@ import { URL } from 'url';
 import { getRequest, setResponse } from '../../../exports/node/index.js';
 import { installPolyfills } from '../../../exports/node/polyfills.js';
 import { coalesce_to_error } from '../../../utils/error.js';
-import { posixify, resolve_entry } from '../../../utils/filesystem.js';
+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';
@@ -302,6 +302,22 @@ export async function dev(vite, vite_config, svelte_config) {
 					);
 				}
 
+				if (decoded === svelte_config.kit.paths.base + '/service-worker.js') {
+					const resolved = resolve_entry(svelte_config.kit.files.serviceWorker);
+
+					if (resolved) {
+						res.writeHead(200, {
+							'content-type': 'application/javascript'
+						});
+						res.end(`import '${to_fs(resolved)}';`);
+					} else {
+						res.writeHead(404);
+						res.end('not found');
+					}
+
+					return;
+				}
+
 				const hooks_file = svelte_config.kit.files.hooks.server;
 				/** @type {Partial<import('types').ServerHooks>} */
 				const user_hooks = resolve_entry(hooks_file)
@@ -451,7 +467,9 @@ export async function dev(vite, vite_config, svelte_config) {
 								.replace(/%sveltekit\.status%/g, String(status))
 								.replace(/%sveltekit\.error\.message%/g, message);
 						},
-						service_worker: false,
+						service_worker:
+							svelte_config.kit.serviceWorker.register &&
+							!!resolve_entry(svelte_config.kit.files.serviceWorker),
 						trailing_slash: svelte_config.kit.trailingSlash
 					},
 					{
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 0a9214c09..179924b71 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -18,6 +18,7 @@ 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';
 
 const cwd = process.cwd();
 
@@ -289,7 +290,7 @@ function kit() {
 
 		async resolveId(id) {
 			// treat $env/static/[public|private] as virtual
-			if (id.startsWith('$env/')) return `\0${id}`;
+			if (id.startsWith('$env/') || id === '$service-worker') return `\0${id}`;
 		},
 
 		async load(id, options) {
@@ -323,6 +324,8 @@ function kit() {
 						'public',
 						vite_config_env.command === 'serve' ? env.public : undefined
 					);
+				case '\0$service-worker':
+					return create_service_worker_module(svelte_config);
 			}
 		},
 
@@ -627,3 +630,22 @@ function find_overridden_config(config, resolved_config, enforced_config, path,
 
 	return out;
 }
+
+/**
+ * @param {import('types').ValidatedConfig} config
+ */
+const create_service_worker_module = (config) => `
+if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) {
+	throw new Error('This module can only be imported inside a service worker');
+}
+
+export const build = [];
+export const files = [
+	${create_assets(config)
+		.filter((asset) => config.kit.serviceWorker.files(asset.file))
+		.map((asset) => `${JSON.stringify(`${config.kit.paths.base}/${asset.file}`)}`)
+		.join(',\n\t\t\t\t')}
+];
+export const prerendered = [];
+export const version = ${JSON.stringify(config.kit.version.name)};
+`;
diff --git a/packages/kit/src/runtime/app/forms.js b/packages/kit/src/runtime/app/forms.js
index 9359f146b..6b1cbacb5 100644
--- a/packages/kit/src/runtime/app/forms.js
+++ b/packages/kit/src/runtime/app/forms.js
@@ -27,7 +27,8 @@ export function enhance(form, submit = () => {}) {
 	const fallback_callback = async ({ action, result, reset }) => {
 		if (result.type === 'success') {
 			if (reset !== false) {
-				form.reset();
+				// We call reset from the prototype to avoid DOM clobbering
+				HTMLFormElement.prototype.reset.call(form);
 			}
 			await invalidateAll();
 		}
@@ -49,9 +50,10 @@ export function enhance(form, submit = () => {}) {
 
 		const action = new URL(
 			// We can't do submitter.formAction directly because that property is always set
+			// We do cloneNode for avoid DOM clobbering - https://github.com/sveltejs/kit/issues/7593
 			event.submitter?.hasAttribute('formaction')
 				? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
-				: form.action
+				: /** @type {HTMLFormElement} */ (HTMLFormElement.prototype.cloneNode.call(form)).action
 		);
 
 		const data = new FormData(form);
@@ -67,13 +69,13 @@ export function enhance(form, submit = () => {}) {
 		const cancel = () => (cancelled = true);
 
 		const callback =
-			submit({
+			(await submit({
 				action,
 				cancel,
 				controller,
 				data,
 				form
-			}) ?? fallback_callback;
+			})) ?? fallback_callback;
 		if (cancelled) return;
 
 		/** @type {import('types').ActionResult} */
@@ -117,11 +119,13 @@ export function enhance(form, submit = () => {}) {
 		});
 	}
 
-	form.addEventListener('submit', handle_submit);
+	// @ts-expect-error
+	HTMLFormElement.prototype.addEventListener.call(form, 'submit', handle_submit);
 
 	return {
 		destroy() {
-			form.removeEventListener('submit', handle_submit);
+			// @ts-expect-error
+			HTMLFormElement.prototype.removeEventListener.call(form, 'submit', handle_submit);
 		}
 	};
 }
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index e7406a57e..583457a93 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -218,7 +218,16 @@ export function create_client({ target, base, trailing_slash }) {
 			throw new Error(`Attempted to prefetch a URL that does not belong to this app: ${url}`);
 		}
 
-		load_cache = { id: intent.id, promise: load_route(intent) };
+		load_cache = {
+			id: intent.id,
+			promise: load_route(intent).then((result) => {
+				if (result.type === 'loaded' && result.state.error) {
+					// Don't cache errors, because they might be transient
+					load_cache = null;
+				}
+				return result;
+			})
+		};
 
 		return load_cache.promise;
 	}
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index e88f2a30f..d0e9ab2dc 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -3,7 +3,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 } from './utils.js';
+import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
 import {
 	decode_pathname,
 	decode_params,
@@ -16,6 +16,7 @@ import { exec } from '../../utils/routing.js';
 import { INVALIDATED_HEADER, render_data } from './data/index.js';
 import { add_cookies_to_headers, get_cookies } from './cookie.js';
 import { create_fetch } from './fetch.js';
+import { Redirect } from '../control.js';
 
 /* global __SVELTEKIT_ADAPTER_NAME__ */
 
@@ -360,6 +361,9 @@ export async function respond(request, options, state) {
 
 		return response;
 	} catch (error) {
+		if (error instanceof Redirect) {
+			return redirect_response(error.status, error.location);
+		}
 		return handle_fatal_error(event, options, error);
 	}
 }
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 48fde85e4..f946d0831 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -288,12 +288,14 @@ export async function render_response({
 	}
 
 	if (options.service_worker) {
+		const opts = options.dev ? `, { type: 'module' }` : '';
+
 		// we use an anonymous function instead of an arrow function to support
 		// older browsers (https://github.com/sveltejs/kit/pull/5417)
 		const init_service_worker = `
 			if ('serviceWorker' in navigator) {
 				addEventListener('load', function () {
-					navigator.serviceWorker.register('${prefixed('service-worker.js')}');
+					navigator.serviceWorker.register('${prefixed('service-worker.js')}'${opts});
 				});
 			}
 		`;
diff --git a/packages/kit/src/utils/filesystem.js b/packages/kit/src/utils/filesystem.js
index 1cd688186..f4030e803 100644
--- a/packages/kit/src/utils/filesystem.js
+++ b/packages/kit/src/utils/filesystem.js
@@ -112,6 +112,18 @@ export function posixify(str) {
 	return str.replace(/\\/g, '/');
 }
 
+/**
+ * Prepend given path with `/@fs` prefix
+ * @param {string} str
+ */
+export function to_fs(str) {
+	str = posixify(str);
+	return `/@fs${
+		// Windows/Linux separation - Windows starts with a drive letter, we need a / in front there
+		str.startsWith('/') ? '' : '/'
+	}${str}`;
+}
+
 /**
  * Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js
  * @param {string} entry
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 2f214d7d1..29fb4ce1b 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -342,6 +342,7 @@ declare module '$app/stores' {
 declare module '$service-worker' {
 	/**
 	 * An array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`.
+	 * During development, this is be an empty array.
 	 */
 	export const build: string[];
 	/**
@@ -350,6 +351,7 @@ declare module '$service-worker' {
 	export const files: string[];
 	/**
 	 * An array of pathnames corresponding to prerendered pages and endpoints.
+	 * During development, this is be an empty array.
 	 */
 	export const prerendered: string[];
 	/**


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte b/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte
index 032b7ca9c..5ee9f544e 100644
--- a/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/actions/enhance/+page.svelte
@@ -14,6 +14,8 @@
 <pre class="formdata2">{JSON.stringify($page.form)}</pre>
 
 <form method="post" action="?/login" use:enhance>
+	<input type=hidden name="action" value="DOM clobbering" />
+	<input type=hidden name="reset" value="DOM clobbering" />
 	<input name="username" type="text" />
 	<button class="form1">Submit</button>
 	<button class="form1-register" formAction="?/register">Submit</button>
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetched.json/+server.js b/packages/kit/test/apps/basics/src/routes/routing/prefetched.json/+server.js
deleted file mode 100644
index 04004e8da..000000000
--- a/packages/kit/test/apps/basics/src/routes/routing/prefetched.json/+server.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { json } from '@sveltejs/kit';
-
-export function GET() {
-	return json('prefetched');
-}
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetched/+page.js b/packages/kit/test/apps/basics/src/routes/routing/prefetched/+page.js
deleted file mode 100644
index 76a9f0c1c..000000000
--- a/packages/kit/test/apps/basics/src/routes/routing/prefetched/+page.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/** @type {import('@sveltejs/kit').Load} */
-export async function load({ fetch }) {
-	const message = await fetch('/routing/prefetched.json').then((r) => r.json());
-	return { message };
-}
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetched/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/prefetched/+page.svelte
deleted file mode 100644
index 65b84bb82..000000000
--- a/packages/kit/test/apps/basics/src/routes/routing/prefetched/+page.svelte
+++ /dev/null
@@ -1,6 +0,0 @@
-<script>
-	/** @type {import('./$types').PageData} */
-	export let data;
-</script>
-
-<h1>{data.message}</h1>
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetched/hash-route/+page.js b/packages/kit/test/apps/basics/src/routes/routing/prefetched/hash-route/+page.js
deleted file mode 100644
index 81da9fc7a..000000000
--- a/packages/kit/test/apps/basics/src/routes/routing/prefetched/hash-route/+page.js
+++ /dev/null
@@ -1,6 +0,0 @@
-let load_calls = 0;
-
-export const load = () => {
-	load_calls += 1;
-	return { calls: load_calls };
-};
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetched/hash-route/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/prefetched/hash-route/+page.svelte
deleted file mode 100644
index dd0428be8..000000000
--- a/packages/kit/test/apps/basics/src/routes/routing/prefetched/hash-route/+page.svelte
+++ /dev/null
@@ -1,29 +0,0 @@
-<script>
-	import { onMount } from 'svelte';
-	import { page } from '$app/stores';
-
-	export let data;
-
-	const modal_contents = {
-		'please-dont-show-me': {
-			title: 'Oopsie'
-		},
-		'please-dont-show-me-jr': {
-			title: 'Oopsie Jr.'
-		}
-	};
-
-	let modal = undefined;
-
-	const show_modal = () => {
-		const hash = $page.url.hash.substring(1);
-		modal = modal_contents[hash];
-	};
-
-	onMount(show_modal);
-</script>
-
-<svelte:window on:popstate={show_modal} />
-
-<h1>{modal?.title ?? ''}</h1>
-<p>Loaded {data.calls} times.</p>
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetch-error/+page.js b/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetch-error/+page.js
new file mode 100644
index 000000000..eed3de964
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetch-error/+page.js
@@ -0,0 +1,9 @@
+let throw_error = false;
+
+/** @type {import('@sveltejs/kit').Load} */
+export async function load() {
+	throw_error = !throw_error;
+	if (throw_error) {
+		throw new Error('error');
+	}
+}
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetch-error/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetch-error/+page.svelte
new file mode 100644
index 000000000..302a01f33
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetch-error/+page.svelte
@@ -0,0 +1,1 @@
+<p>hello</p>
diff --git a/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetched/+page.js b/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetched/+page.js
index 76a9f0c1c..2c658c282 100644
--- a/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetched/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/routing/prefetching/prefetched/+page.js
@@ -1,5 +1,5 @@
 /** @type {import('@sveltejs/kit').Load} */
 export async function load({ fetch }) {
-	const message = await fetch('/routing/prefetched.json').then((r) => r.json());
+	const message = await fetch('/routing/prefetching/prefetched.json').then((r) => r.json());
 	return { message };
 }
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index 2b89f27f1..f0c84475d 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -666,8 +666,8 @@ test.describe('Prefetching', () => {
 		// also wait for network processing to complete, see
 		// https://playwright.dev/docs/network#network-events
 		await Promise.all([
-			page.waitForResponse(`${baseURL}/routing/prefetched.json`),
-			app.prefetch('/routing/prefetched')
+			page.waitForResponse(`${baseURL}/routing/prefetching/prefetched.json`),
+			app.prefetch('/routing/prefetching/prefetched')
 		]);
 
 		// svelte request made is environment dependent
@@ -679,10 +679,10 @@ test.describe('Prefetching', () => {
 			expect(requests.filter((req) => req.endsWith('.js')).length).toBeGreaterThan(0);
 		}
 
-		expect(requests.includes(`${baseURL}/routing/prefetched.json`)).toBe(true);
+		expect(requests.includes(`${baseURL}/routing/prefetching/prefetched.json`)).toBe(true);
 
 		requests = [];
-		await app.goto('/routing/prefetched');
+		await app.goto('/routing/prefetching/prefetched');
 		expect(requests).toEqual([]);
 
 		try {
@@ -698,28 +698,36 @@ test.describe('Prefetching', () => {
 		page
 	}) => {
 		await page.goto('/routing/a');
-		await app.prefetch('/routing/prefetched/hash-route#please-dont-show-me');
-		await app.goto('/routing/prefetched/hash-route');
+		await app.prefetch('/routing/prefetching/hash-route#please-dont-show-me');
+		await app.goto('/routing/prefetching/hash-route');
 		await expect(page.locator('h1')).not.toHaveText('Oopsie');
 	});
 
 	test('does not rerun load on calls to duplicate preload hash route', async ({ app, page }) => {
 		await page.goto('/routing/a');
 
-		await app.prefetch('/routing/prefetched/hash-route#please-dont-show-me');
-		await app.prefetch('/routing/prefetched/hash-route#please-dont-show-me');
-		await app.goto('/routing/prefetched/hash-route#please-dont-show-me');
+		await app.prefetch('/routing/prefetching/hash-route#please-dont-show-me');
+		await app.prefetch('/routing/prefetching/hash-route#please-dont-show-me');
+		await app.goto('/routing/prefetching/hash-route#please-dont-show-me');
 		await expect(page.locator('p')).toHaveText('Loaded 1 times.');
 	});
 
 	test('does not rerun load on calls to different preload hash route', async ({ app, page }) => {
 		await page.goto('/routing/a');
 
-		await app.prefetch('/routing/prefetched/hash-route#please-dont-show-me');
-		await app.prefetch('/routing/prefetched/hash-route#please-dont-show-me-jr');
-		await app.goto('/routing/prefetched/hash-route#please-dont-show-me');
+		await app.prefetch('/routing/prefetching/hash-route#please-dont-show-me');
+		await app.prefetch('/routing/prefetching/hash-route#please-dont-show-me-jr');
+		await app.goto('/routing/prefetching/hash-route#please-dont-show-me');
 		await expect(page.locator('p')).toHaveText('Loaded 1 times.');
 	});
+
+	test('does rerun load when prefetch errored', async ({ app, page }) => {
+		await page.goto('/routing/a');
+
+		await app.prefetch('/routing/prefetching/prefetch-error');
+		await app.goto('/routing/prefetching/prefetch-error');
+		await expect(page.locator('p')).toHaveText('hello');
+	});
 });
 
 test.describe('Routing', () => {