Repository: sveltejs/kit (js)

Issue:
Cache problem with client-side navigation: the response is now __data.js instead of __data.json
### Describe the bug

I believe the recent change where client-side navigation responds with __data.js instead of __data.json can be problematic.

I have Cloudflare on my site and I just realized that the __data.js files that are returned when navigating between pages are being cached on the edge and possibly also in the browser cache. I don't have any custom page rules for .js files in Cloudflare, this is the default behavior.

In some cases, the "age" in the response headers is showing very high numbers, much higher than what I have in my s-max-age cache-control headers in hooks.js (which used to cache on Vercel edge only, not Cloudflare).

![681e4b3be00bfa462433bc8450004e6b](https://user-images.githubusercontent.com/22234692/187604022-d8085af7-1ebe-4a9a-a157-5d79be60fd98.png)

This means that many users are potentially seeing very old and outdated info for pages that are supposed to be highly dynamic.

### Reproduction

I don't think I can set up a reproduction since it would require a custom domain and adding it to Cloudflare.

### Logs

_No response_

### System Info

```shell
System:
    OS: Windows 10 10.0.22000
    CPU: (32) x64 AMD Ryzen 9 5950X 16-Core Processor
    Memory: 50.08 GB / 63.91 GB
  Binaries:
    Node: 16.16.0 - C:\Program Files\nodejs\node.EXE
    npm: 8.11.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.22000.120.0), Chromium (104.0.1293.70)
    Internet Explorer: 11.0.22000.120
  npmPackages:
    @sveltejs/adapter-auto: 1.0.0-next.69 => 1.0.0-next.70
    @sveltejs/kit: 1.0.0-next.454 => 1.0.0-next.456
    svelte: ^3.49.0 => 3.49.0
    vite: 3.1.0-beta.1 => 3.1.0-beta.1
```


### Severity

Blocking an upgrade

### Additional Information

Update: I rolled back to 446 for now since the 447 update seems to also have introduced a bunch of weird issues with both Sentry and Plausible Analytics.

Pull request description:
prevent caching of __data.js files
closes #6458. This implements https://github.com/sveltejs/kit/pull/6809#issuecomment-1246958072, minus the `ttl` stuff which should be a separate issue

### 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/cool-beans-only.md b/.changeset/cool-beans-only.md
new file mode 100644
index 000000000..755343640
--- /dev/null
+++ b/.changeset/cool-beans-only.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": patch
+---
+
+fix `write_types` on windows using posixify()
diff --git a/.changeset/cyan-years-live.md b/.changeset/cyan-years-live.md
new file mode 100644
index 000000000..0ed5696bb
--- /dev/null
+++ b/.changeset/cyan-years-live.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Prevent caching of `__data.js` files
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 2bb940b06..ff8dd1048 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -981,6 +981,7 @@
     "silver-hounds-clean",
     "silver-lemons-fail",
     "silver-needles-judge",
+    "silver-pugs-worry",
     "silver-rice-flash",
     "silver-scissors-peel",
     "silver-toes-cheer",
diff --git a/.changeset/silver-pugs-worry.md b/.changeset/silver-pugs-worry.md
new file mode 100644
index 000000000..27e98e25a
--- /dev/null
+++ b/.changeset/silver-pugs-worry.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] tighten up handling and documentation around 404
diff --git a/.changeset/young-students-relate.md b/.changeset/young-students-relate.md
new file mode 100644
index 000000000..06b05a32b
--- /dev/null
+++ b/.changeset/young-students-relate.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Make url property getters non-enumerable
diff --git a/documentation/docs/04-advanced-routing.md b/documentation/docs/04-advanced-routing.md
index 05c1c813f..2f9d67abe 100644
--- a/documentation/docs/04-advanced-routing.md
+++ b/documentation/docs/04-advanced-routing.md
@@ -22,7 +22,11 @@ If the number of route segments is unknown, you can use rest syntax — for exam
 }
 ```
 
-This also allows you to render custom 404s. Given these routes...
+> `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#matching).
+
+#### 404 pages
+
+Rest parameters also allow you to render custom 404s. Given these routes...
 
 ```
 src/routes/
@@ -47,7 +51,17 @@ src/routes/
 └ +error.svelte
 ```
 
-> `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#matching).
+```js
+/// file: src/routes/marx-brothers/[...path]/+page.js
+import { error } from '@sveltejs/kit';
+
+/** @type {import('./$types').PageLoad} */
+export function load(event) {
+	throw error(404, 'Not Found');
+}
+```
+
+> If you don't handle 404 cases, they will appear in [`handleError`](/docs/hooks#shared-hooks-handleerror)
 
 ### Matching
 
diff --git a/documentation/docs/07-hooks.md b/documentation/docs/07-hooks.md
index 1b1dd3adb..8ede315c3 100644
--- a/documentation/docs/07-hooks.md
+++ b/documentation/docs/07-hooks.md
@@ -136,7 +136,7 @@ The following can be added to `src/hooks.server.js` _and_ `src/hooks.client.js`:
 If an unexpected error is thrown during loading or rendering, this function will be called with the `error` and the `event`. This allows for two things:
 
 - you can log the error
-- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message: 'Internal Error' }`, becomes the value of `$page.error`. To make this type-safe, you can customize the expected shape by declaring an `App.PageError` interface (which must include `message: string`, to guarantee sensible fallback behavior).
+- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value becomes the value of `$page.error`. It defaults to `{ message: 'Not Found' }` in case of a 404 (you can detect them through `event.routeId` being `null`) and to `{ message: 'Internal Error' }` for everything else. To make this type-safe, you can customize the expected shape by declaring an `App.PageError` interface (which must include `message: string`, to guarantee sensible fallback behavior).
 
 The following code shows an example of typing the error shape as `{ message: string; code: string }` and returning it accordingly from the `handleError` functions:
 
diff --git a/package.json b/package.json
index 817041bcc..6109081fc 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
 		"turbo": "^1.3.4",
 		"typescript": "^4.8.2"
 	},
-	"packageManager": "pnpm@7.12.0",
+	"packageManager": "pnpm@7.12.1",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 583f6325b..6724737a4 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/kit
 
+## 1.0.0-next.489
+
+### Patch Changes
+
+- [fix] tighten up handling and documentation around 404 ([#6897](https://github.com/sveltejs/kit/pull/6897))
+
 ## 1.0.0-next.488
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index bcd67f0d0..6e8a24bb0 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.488",
+	"version": "1.0.0-next.489",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js
index 5204a8de5..a7c82f905 100644
--- a/packages/kit/src/core/sync/write_types/index.js
+++ b/packages/kit/src/core/sync/write_types/index.js
@@ -68,7 +68,7 @@ export async function write_types(config, manifest_data, file) {
 		return;
 	}
 
-	const id = path.posix.relative(config.kit.files.routes, path.dirname(file));
+	const id = posixify(path.relative(config.kit.files.routes, path.dirname(file)));
 
 	const route = manifest_data.routes.find((route) => route.id === id);
 	if (!route) return; // this shouldn't ever happen
diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js
index 77011b11e..1b53cd9fb 100644
--- a/packages/kit/src/exports/vite/build/build_server.js
+++ b/packages/kit/src/exports/vite/build/build_server.js
@@ -68,7 +68,7 @@ export class Server {
 					get request() {
 						throw new Error('request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details');
 					}
-				}) ?? { message: 'Internal Error' };
+				}) ?? { message: event.routeId ? 'Internal Error' : 'Not Found' };
 			},
 			hooks: null,
 			manifest,
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index 3c28acb48..f9aeb8f3b 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -350,7 +350,7 @@ export async function dev(vite, vite_config, svelte_config) {
 						user_hooks.handleError ||
 						(({ error: e }) => {
 							const error = /** @type {Error & { frame?: string }} */ (e);
-							console.error(colors.bold().red(error.message));
+							console.error(colors.bold().red(error.message ?? error)); // Could be anything
 							if (error.frame) {
 								console.error(colors.gray(error.frame));
 							}
@@ -432,7 +432,7 @@ export async function dev(vite, vite_config, svelte_config) {
 											'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
 										);
 									}
-								}) ?? { message: 'Internal Error' }
+								}) ?? { message: event.routeId ? 'Internal Error' : 'Not Found' }
 							);
 						},
 						hooks,
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 106b0c918..d62020e1e 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -1513,7 +1513,10 @@ async function load_data(url, invalid) {
  * @returns {App.PageError}
  */
 function handle_error(error, event) {
-	return hooks.handleError({ error, event }) ?? /** @type {any} */ ({ message: 'Internal Error' });
+	return (
+		hooks.handleError({ error, event }) ??
+		/** @type {any} */ ({ message: event.routeId ? 'Internal Error' : 'Not Found' })
+	);
 }
 
 // TODO remove for 1.0
@@ -1543,7 +1546,8 @@ function add_url_properties(type, target) {
 				throw new Error(
 					`The navigation shape changed - ${type}.${prop} should now be ${type}.url.${prop}`
 				);
-			}
+			},
+			enumerable: false
 		});
 	}
 
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 b0e790f02..76fb26f60 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -4,9 +4,11 @@ import {
 	handle_error_and_jsonify,
 	GENERIC_ERROR,
 	get_option,
-	static_error_page
+	static_error_page,
+	redirect_response
 } from '../utils.js';
 import { create_fetch } from './fetch.js';
+import { HttpError, Redirect } from '../../control.js';
 
 /**
  * @typedef {import('./types.js').Loaded} Loaded
@@ -87,6 +89,16 @@ export async function respond_with_error({ event, options, state, status, error,
 			resolve_opts
 		});
 	} catch (error) {
-		return static_error_page(options, 500, handle_error_and_jsonify(event, options, error).message);
+		// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
+		// we end up here.
+		if (error instanceof Redirect) {
+			return redirect_response(error.status, error.location, cookies);
+		}
+
+		return static_error_page(
+			options,
+			error instanceof HttpError ? error.status : 500,
+			handle_error_and_jsonify(event, options, error).message
+		);
 	}
 }
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index 333491328..c137f9f4f 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -70,21 +70,18 @@ export function allowed_methods(mod) {
 
 /** @param {any} data */
 export function data_response(data) {
+	const headers = {
+		'content-type': 'application/javascript',
+		'cache-control': 'private, no-store'
+	};
+
 	try {
-		return new Response(`window.__sveltekit_data = ${devalue(data)}`, {
-			headers: {
-				'content-type': 'application/javascript'
-			}
-		});
+		return new Response(`window.__sveltekit_data = ${devalue(data)}`, { headers });
 	} catch (e) {
 		const error = /** @type {any} */ (e);
 		const match = /\[(\d+)\]\.data\.(.+)/.exec(error.path);
 		const message = match ? `${error.message} (data.${match[2]})` : error.message;
-		return new Response(`throw new Error(${JSON.stringify(message)})`, {
-			headers: {
-				'content-type': 'application/javascript'
-			}
-		});
+		return new Response(`throw new Error(${JSON.stringify(message)})`, { headers });
 	}
 }
 


Test patch:
diff --git a/packages/kit/test/apps/basics/src/hooks.client.js b/packages/kit/test/apps/basics/src/hooks.client.js
index 62ee8c1eb..09bfd498b 100644
--- a/packages/kit/test/apps/basics/src/hooks.client.js
+++ b/packages/kit/test/apps/basics/src/hooks.client.js
@@ -1,4 +1,6 @@
 /** @type{import("@sveltejs/kit").HandleClientError} */
-export function handleError({ error }) {
-	return { message: /** @type {Error} */ (error).message };
+export function handleError({ error, event }) {
+	return event.url.pathname.endsWith('404-fallback')
+		? undefined
+		: { message: /** @type {Error} */ (error).message };
 }
diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js
index cb085363b..bf5d6b7fe 100644
--- a/packages/kit/test/apps/basics/src/hooks.server.js
+++ b/packages/kit/test/apps/basics/src/hooks.server.js
@@ -33,7 +33,7 @@ export const handleError = ({ event, error: e }) => {
 		: {};
 	errors[event.url.pathname] = error_to_pojo(error);
 	fs.writeFileSync('test/errors.json', JSON.stringify(errors));
-	return { message: error.message };
+	return event.url.pathname.endsWith('404-fallback') ? undefined : { message: error.message };
 };
 
 export const handle = sequence(
diff --git a/packages/kit/test/apps/basics/src/routes/+layout.server.js b/packages/kit/test/apps/basics/src/routes/+layout.server.js
index cb10abf04..001350db5 100644
--- a/packages/kit/test/apps/basics/src/routes/+layout.server.js
+++ b/packages/kit/test/apps/basics/src/routes/+layout.server.js
@@ -1,6 +1,8 @@
-let should_fail = false;
+import { error, redirect } from '@sveltejs/kit';
+
+let should_fail = '';
 /**
- * @param {boolean} value
+ * @param {string} value
  */
 export function set_should_fail(value) {
 	should_fail = value;
@@ -8,8 +10,16 @@ export function set_should_fail(value) {
 
 export async function load() {
 	if (should_fail) {
-		set_should_fail(false);
-		throw new Error('Failed to load');
+		if (should_fail === 'expected') {
+			set_should_fail('');
+			throw error(401, 'Not allowed');
+		} else if (should_fail === 'unexpected') {
+			set_should_fail('');
+			throw new Error('Failed to load');
+		} else {
+			set_should_fail('');
+			throw redirect(307, '/load');
+		}
 	}
 	// Do NOT make this load function depend on something which would cause it to rerun
 	return {
diff --git a/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte
index 2fda2e2e0..b8cf7fcdf 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/errors/error-html/+page.svelte
@@ -1,8 +1,15 @@
 <script>
-	async function fail() {
-		await fetch('./error-html/make-root-fail');
-		location.reload();
+	/** @param {string} type */
+	async function fail(type) {
+		await fetch(`/errors/error-html/make-root-fail?type=${type}`);
+		if (type === 'redirect') {
+			location.assign(location.href + '/404');
+		} else {
+			location.reload();
+		}
 	}
 </script>
 
-<button on:click={fail}>Let's fail catastrophically</button>
+<button on:click={() => fail('unexpected')}>Unexpected</button>
+<button on:click={() => fail('expected')}>Expected</button>
+<button on:click={() => fail('redirect')}>Redirect</button>
diff --git a/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js b/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js
index 7c3f14cfd..c3030a9c9 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js
+++ b/packages/kit/test/apps/basics/src/routes/errors/error-html/make-root-fail/+server.js
@@ -1,6 +1,9 @@
 import { set_should_fail } from '../../../+layout.server';
 
-export function GET() {
-	set_should_fail(true);
+/**
+ * @type {import('./$types').RequestHandler} param0
+ */
+export function GET({ url }) {
+	set_should_fail(url.searchParams.get('type'));
 	return new Response();
 }
diff --git a/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.server.js
new file mode 100644
index 000000000..01e1a121a
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.server.js
@@ -0,0 +1,6 @@
+/** @type {import('./$types').PageServerLoad} */
+export function load({ url }) {
+	return {
+		x: url.searchParams.get('x')
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.svelte
new file mode 100644
index 000000000..9dc41ad6f
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/server-data-nostore/+page.svelte
@@ -0,0 +1,7 @@
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>x: {data.x}</h1>
+<a href="/load/server-data-nostore?x=2">2</a>
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index 42dedf5b7..628e64dd1 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -263,7 +263,7 @@ test.describe('Endpoints', () => {
 	});
 });
 
-test.describe('Errors', () => {
+test.describe.serial('Errors', () => {
 	test('client-side load errors', async ({ page }) => {
 		await page.goto('/errors/load-client');
 
@@ -302,14 +302,29 @@ test.describe('Errors', () => {
 		expect(await page.innerHTML('h1')).toBe('401');
 	});
 
-	test('Root error falls back to error.html', async ({ page }) => {
+	test('Root error falls back to error.html (unexpected error)', async ({ page }) => {
 		await page.goto('/errors/error-html');
-		await page.click('button');
+		await page.click('button:text-is("Unexpected")');
 		expect(await page.textContent('h1')).toBe('Error - 500');
 		expect(await page.textContent('p')).toBe(
 			'This is the static error page with the following message: Failed to load'
 		);
 	});
+
+	test('Root error falls back to error.html (expected error)', async ({ page }) => {
+		await page.goto('/errors/error-html');
+		await page.click('button:text-is("Expected")');
+		expect(await page.textContent('h1')).toBe('Error - 401');
+		expect(await page.textContent('p')).toBe(
+			'This is the static error page with the following message: Not allowed'
+		);
+	});
+
+	test('Root 404 redirects somewhere due to root layout', async ({ page, baseURL }) => {
+		await page.goto('/errors/error-html');
+		await Promise.all([page.waitForNavigation(), page.click('button:text-is("Redirect")')]);
+		expect(page.url()).toBe(baseURL + '/load');
+	});
 });
 
 test.describe('Load', () => {
@@ -416,6 +431,17 @@ test.describe('Load', () => {
 		await expect(page.locator('p')).toHaveText('Count is 2');
 	});
 
+	test('__data.js has cache-control: private, no-store', async ({ page, clicknav }) => {
+		await page.goto('/load/server-data-nostore?x=1');
+
+		const [response] = await Promise.all([
+			page.waitForResponse((response) => /__data\.js/.test(response.url())),
+			clicknav('[href="/load/server-data-nostore?x=2"]')
+		]);
+
+		expect(response.headers()['cache-control']).toBe('private, no-store');
+	});
+
 	if (process.env.DEV) {
 		test('using window.fetch causes a warning', async ({ page }) => {
 			const port = 5173;
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 15c03f300..1ce41289c 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -1633,6 +1633,12 @@ test.describe('Routing', () => {
 		await page.goto('/static');
 		expect(await page.textContent('h1')).toBe('hello');
 	});
+
+	test('shows "Not Found" in 404 case', async ({ page }) => {
+		await page.goto('/404-fallback');
+		expect(await page.textContent('h1')).toBe('404');
+		expect(await page.textContent('p')).toBe('This is your custom error page saying: "Not Found"');
+	});
 });
 
 test.describe('Matchers', () => {