Repository: sveltejs/kit
Pull request #7314 (2022-10-19 14:59:11)

chore: improve error message for prefetching invalid url
Happened to come across `Uncaught (in promise) Error: Attempted to prefetch a URL that does not belong to this app ` in an app today and didn't immediately find the cause.

The url in the message can help finding where in the application it happened, also added some guidance and docs link for good measure.

In my case it was a link to a page that hadn't been created yet. If thats the only way to reach this error, maybe the url is enough to not confuse users into disabling prefetching too eagerly?


### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] 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
- [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


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

Latest commit: 052932e9402b13be3fb10290d8af1435845b9b3d

**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/dominikg/kit/new/chore/improve-error-msg?filename=.changeset/four-ants-kick.md&value=---%0A%22%40sveltejs%2Fkit%22%3A%20patch%0A---%0A%0Achore%3A%20improve%20error%20message%20for%20prerendering%20invalid%20url%0A)



Patch:
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 6cd5be767..33c83cdf1 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -1165,6 +1165,7 @@
     "strong-cooks-run",
     "strong-cooks-turn",
     "strong-cups-yell",
+    "strong-experts-begin",
     "strong-files-obey",
     "strong-kangaroos-work",
     "strong-laws-press",
diff --git a/.changeset/strong-experts-begin.md b/.changeset/strong-experts-begin.md
new file mode 100644
index 000000000..287b1cd08
--- /dev/null
+++ b/.changeset/strong-experts-begin.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] ensure serialized headers check is always applied
diff --git a/.changeset/twenty-monkeys-glow.md b/.changeset/twenty-monkeys-glow.md
new file mode 100644
index 000000000..9a3d4d1a5
--- /dev/null
+++ b/.changeset/twenty-monkeys-glow.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Improve error message when prefetching fails
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index b5a4c39bb..a94d39153 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -26,13 +26,13 @@ body:
     attributes:
       label: Logs
       description: "Please include browser console and server logs around the time this bug occurred. Please try not to insert an image but copy paste the log text."
-      render: shell
+      render: Shell
   - type: textarea
     id: system-info
     attributes:
       label: System Info
       description: Output of `npx envinfo --system --binaries --browsers --npmPackages "{svelte,@sveltejs/*,vite}"`
-      render: shell
+      render: Shell
       placeholder: System, Binaries, Browsers
     validations:
       required: true
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 33678ed9a..ff0f6afd6 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/kit
 
+## 1.0.0-next.520
+
+### Patch Changes
+
+- [fix] ensure serialized headers check is always applied ([#7221](https://github.com/sveltejs/kit/pull/7221))
+
 ## 1.0.0-next.519
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 184de3219..9dcc7eea0 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.519",
+	"version": "1.0.0-next.520",
 	"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 837fdf389..70287c910 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -199,7 +199,7 @@ export function create_client({ target, base, trailing_slash }) {
 		const intent = get_navigation_intent(url, false);
 
 		if (!intent) {
-			throw new Error('Attempted to prefetch a URL that does not belong to this app');
+			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) };
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index 954b4e6b7..e8bef275c 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -160,6 +160,8 @@ export async function render_page(event, route, page, options, state, resolve_op
 			});
 		});
 
+		const csr = get_option(nodes, 'csr') ?? true;
+
 		/** @type {Array<Promise<Record<string, any> | null>>} */
 		const load_promises = nodes.map((node, i) => {
 			if (load_error) throw load_error;
@@ -178,7 +180,8 @@ export async function render_page(event, route, page, options, state, resolve_op
 						},
 						resolve_opts,
 						server_data_promise: server_promises[i],
-						state
+						state,
+						csr
 					});
 				} catch (e) {
 					load_error = /** @type {Error} */ (e);
diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js
index 63cffc336..aeffbe426 100644
--- a/packages/kit/src/runtime/server/page/load_data.js
+++ b/packages/kit/src/runtime/server/page/load_data.js
@@ -74,6 +74,7 @@ export async function load_server_data({ event, state, node, parent }) {
  *   resolve_opts: import('types').RequiredResolveOptions;
  *   server_data_promise: Promise<import('types').ServerDataNode | null>;
  *   state: import('types').SSRState;
+ *   csr: boolean;
  * }} opts
  * @returns {Promise<Record<string, any> | null>}
  */
@@ -84,7 +85,8 @@ export async function load_data({
 	parent,
 	server_data_promise,
 	state,
-	resolve_opts
+	resolve_opts,
+	csr
 }) {
 	const server_data_node = await server_data_promise;
 
@@ -129,23 +131,6 @@ export async function load_data({
 								response_body: body,
 								response: response
 							});
-
-							// ensure that excluded headers can't be read
-							const get = response.headers.get;
-							response.headers.get = (key) => {
-								const lower = key.toLowerCase();
-								const value = get.call(response.headers, lower);
-								if (value && !lower.startsWith('x-sveltekit-')) {
-									const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
-									if (!included) {
-										throw new Error(
-											`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#handle`
-										);
-									}
-								}
-
-								return value;
-							};
 						}
 
 						if (dependency) {
@@ -186,6 +171,25 @@ export async function load_data({
 				}
 			});
 
+			if (csr) {
+				// ensure that excluded headers can't be read
+				const get = response.headers.get;
+				response.headers.get = (key) => {
+					const lower = key.toLowerCase();
+					const value = get.call(response.headers, lower);
+					if (value && !lower.startsWith('x-sveltekit-')) {
+						const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
+						if (!included) {
+							throw new Error(
+								`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#handle (at ${event.routeId})`
+							);
+						}
+					}
+
+					return value;
+				};
+			}
+
 			return proxy;
 		},
 		setHeaders: event.setHeaders,
diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js
index c61f1a263..8d68ad006 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -33,6 +33,7 @@ export async function respond_with_error({ event, options, state, status, error,
 		const branch = [];
 		const default_layout = await options.manifest._.nodes[0](); // 0 is always the root layout
 		const ssr = get_option([default_layout], 'ssr') ?? true;
+		const csr = get_option([default_layout], 'csr') ?? true;
 
 		if (ssr) {
 			state.initiator = GENERIC_ERROR;
@@ -53,7 +54,8 @@ export async function respond_with_error({ event, options, state, status, error,
 				parent: async () => ({}),
 				resolve_opts,
 				server_data_promise,
-				state
+				state,
+				csr
 			});
 
 			branch.push(


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/load/fetch-request-headers-invalid-access/+page.js b/packages/kit/test/apps/basics/src/routes/load/fetch-request-headers-invalid-access/+page.js
new file mode 100644
index 000000000..eda2a2da8
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/fetch-request-headers-invalid-access/+page.js
@@ -0,0 +1,8 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ fetch }) {
+	let res = await fetch('./irrelevant', {
+		method: 'GET'
+	});
+
+	res.headers.get('content-type');
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/fetch-request-headers-invalid-access/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/fetch-request-headers-invalid-access/+page.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 9331e7708..c0f2325d8 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -851,6 +851,17 @@ test.describe('Load', () => {
 		}
 	});
 
+	test('errors when trying to access non-serialized request headers on the server', async ({
+		page,
+		read_errors
+	}) => {
+		await page.goto('/load/fetch-request-headers-invalid-access');
+
+		expect(read_errors(`/load/fetch-request-headers-invalid-access`).message).toContain(
+			'Failed to get response header "content-type" — it must be included by the `filterSerializedResponseHeaders` option'
+		);
+	});
+
 	test('exposes rawBody as a DataView to endpoints', async ({ page, clicknav }) => {
 		await page.goto('/load/raw-body');
 		await clicknav('[href="/load/raw-body/dataview"]');
diff --git a/packages/kit/test/prerendering/basics/src/hooks.server.js b/packages/kit/test/prerendering/basics/src/hooks.server.js
index b27a99e8a..c6c2bff61 100644
--- a/packages/kit/test/prerendering/basics/src/hooks.server.js
+++ b/packages/kit/test/prerendering/basics/src/hooks.server.js
@@ -12,5 +12,7 @@ export const handle = async ({ event, resolve }) => {
 					.replace('__PRERENDERING__', String(prerendering))
 		});
 	}
-	return await resolve(event);
+	return await resolve(event, {
+		filterSerializedResponseHeaders: (name) => name === 'content-type'
+	});
 };