Repository: sveltejs/kit (js)

Issue:
Vercel edge functions producing a null error from @sveltejs/kit next.433
### Describe the bug

@sveltejs/kit next.433 (this PR: https://github.com/sveltejs/kit/pull/6179) seems to have introduced a bug with Vercel edge functions. The edge functions consistently crash

### Reproduction

1. kit 433 with Vercel edge functions: https://github.com/SG60/kit-433-error/tree/36670af2556b9b832b4b7fc47be807a7bc120e0d
_this has an error_
3. kit 433 without edge functions: https://github.com/SG60/kit-433-error/tree/b2e212d73dd297ac54fece7ec8e834657b9967dc
_this works_
5. kit downgraded to 432 with Vercel edge functions: https://github.com/SG60/kit-433-error/tree/c02648f00e9d9812b4dfdaa74a00cab0f93c930e
_this works_

### Logs
(These are from Vercel's function logs)
```shell
TypeError: Cannot read properties of null (reading 'handleError')
    at ../../../../.svelte-kit/output/server/index.js:2455:27
    at ../../../../.svelte-kit/output/server/index.js:2404:12

[GET] / reason=EDGE_FUNCTION_INVOCATION_FAILED, status=500, user_error=true
```


### System Info

```shell
Vercel
```


### Severity

blocking an upgrade

### Additional Information

_No response_

Pull request:
Run server.init() for edge functions on Vercel
Required since #6179.

This should close #6308.

### 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.
- [ ] 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/funny-keys-appear.md b/.changeset/funny-keys-appear.md
new file mode 100644
index 000000000..c6c5fdc29
--- /dev/null
+++ b/.changeset/funny-keys-appear.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/kit": patch
+---
+
+set errors on root component
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 9da888832..f8c7a9eba 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -320,6 +320,7 @@
     "friendly-eggs-cry",
     "friendly-rivers-argue",
     "funny-ads-move",
+    "funny-keys-appear",
     "funny-needles-talk",
     "funny-radios-attend",
     "funny-terms-repair",
@@ -1036,6 +1037,7 @@
     "tame-rings-cry",
     "tasty-cars-love",
     "tasty-donkeys-wait",
+    "tasty-geese-hug",
     "tasty-islands-drive",
     "tasty-squids-share",
     "tasty-zebras-guess",
diff --git a/.changeset/strong-kangaroos-work.md b/.changeset/strong-kangaroos-work.md
new file mode 100644
index 000000000..7708e75fd
--- /dev/null
+++ b/.changeset/strong-kangaroos-work.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/adapter-vercel': patch
+'@sveltejs/adapter-netlify': patch
+---
+
+Run `server.init()` for edge functions
diff --git a/.changeset/tasty-geese-hug.md b/.changeset/tasty-geese-hug.md
new file mode 100644
index 000000000..5fd7eee93
--- /dev/null
+++ b/.changeset/tasty-geese-hug.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] do not call server and update `$page.data` unnecessarily
diff --git a/.changeset/two-kings-worry.md b/.changeset/two-kings-worry.md
new file mode 100644
index 000000000..163e091ca
--- /dev/null
+++ b/.changeset/two-kings-worry.md
@@ -0,0 +1,6 @@
+---
+'@sveltejs/kit': patch
+'create-svelte': patch
+---
+
+[breaking] rename `$app/env` to `$app/environment`, to disambiguate with `$env/...`
diff --git a/.changeset/weak-feet-cheer.md b/.changeset/weak-feet-cheer.md
new file mode 100644
index 000000000..8c056bdd4
--- /dev/null
+++ b/.changeset/weak-feet-cheer.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+Add svelte-kit sync to check scripts in checkjs templates
diff --git a/documentation/docs/80-migrating.md b/documentation/docs/80-migrating.md
index ce59f49a1..cde26307d 100644
--- a/documentation/docs/80-migrating.md
+++ b/documentation/docs/80-migrating.md
@@ -162,7 +162,7 @@ declare module 'html-minifier';
 // @filename: index.js
 // ---cut---
 import { minify } from 'html-minifier';
-import { prerendering } from '$app/env';
+import { prerendering } from '$app/environment';
 
 const minification_options = {
 	collapseBooleanAttributes: true,
diff --git a/documentation/faq/80-integrations.md b/documentation/faq/80-integrations.md
index 09b016da2..10991dad5 100644
--- a/documentation/faq/80-integrations.md
+++ b/documentation/faq/80-integrations.md
@@ -48,7 +48,7 @@ If you need access to the `document` or `window` variables or otherwise need cod
 ```js
 /// <reference types="@sveltejs/kit" />
 // ---cut---
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 if (browser) {
 	// client-only code here
diff --git a/packages/adapter-netlify/src/edge.js b/packages/adapter-netlify/src/edge.js
index 44692e5fd..0acf8a593 100644
--- a/packages/adapter-netlify/src/edge.js
+++ b/packages/adapter-netlify/src/edge.js
@@ -4,6 +4,11 @@ import { manifest, prerendered } from 'MANIFEST';
 const server = new Server(manifest);
 const prefix = `/${manifest.appDir}/`;
 
+await server.init({
+	// @ts-ignore
+	env: Deno.env.toObject()
+});
+
 /**
  * @param { Request } request
  * @param { any } context
diff --git a/packages/adapter-netlify/tsconfig.json b/packages/adapter-netlify/tsconfig.json
index 87bea241e..0a6b886c8 100644
--- a/packages/adapter-netlify/tsconfig.json
+++ b/packages/adapter-netlify/tsconfig.json
@@ -4,7 +4,8 @@
 		"checkJs": true,
 		"noEmit": true,
 		"noImplicitAny": true,
-		"module": "es2020",
+		"module": "es2022",
+		"target": "es2022",
 		"moduleResolution": "node",
 		"allowSyntheticDefaultImports": true,
 		"baseUrl": ".",
diff --git a/packages/adapter-vercel/files/edge.js b/packages/adapter-vercel/files/edge.js
index cc8cb6437..652f56cc7 100644
--- a/packages/adapter-vercel/files/edge.js
+++ b/packages/adapter-vercel/files/edge.js
@@ -3,6 +3,10 @@ import { manifest } from 'MANIFEST';
 
 const server = new Server(manifest);
 
+await server.init({
+	env: process.env
+});
+
 /**
  * @param {Request} request
  */
diff --git a/packages/create-svelte/shared/+checkjs/package.json b/packages/create-svelte/shared/+checkjs/package.json
index ae2ee38d5..ccd4dd126 100644
--- a/packages/create-svelte/shared/+checkjs/package.json
+++ b/packages/create-svelte/shared/+checkjs/package.json
@@ -1,7 +1,7 @@
 {
 	"scripts": {
-		"check": "svelte-check --tsconfig ./jsconfig.json",
-		"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch"
+		"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
+		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
 	},
 	"devDependencies": {
 		"typescript": "^4.7.4",
diff --git a/packages/create-svelte/templates/default/src/routes/about/+page.ts b/packages/create-svelte/templates/default/src/routes/about/+page.ts
index 3e094ec8e..d08ebec1b 100644
--- a/packages/create-svelte/templates/default/src/routes/about/+page.ts
+++ b/packages/create-svelte/templates/default/src/routes/about/+page.ts
@@ -1,4 +1,4 @@
-import { browser, dev } from '$app/env';
+import { browser, dev } from '$app/environment';
 
 // we don't need any JS on this page, though we'll load
 // it in dev so that we get hot module replacement...
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 0998b3039..598175725 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,13 @@
 # @sveltejs/kit
 
+## 1.0.0-next.444
+
+### Patch Changes
+
+- set errors on root component ([#6330](https://github.com/sveltejs/kit/pull/6330))
+
+* [fix] do not call server and update `$page.data` unnecessarily ([#6311](https://github.com/sveltejs/kit/pull/6311))
+
 ## 1.0.0-next.443
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index ec73fb650..0f4d7b5df 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.443",
+	"version": "1.0.0-next.444",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js
index 25456c6c6..ff200ae13 100644
--- a/packages/kit/src/core/sync/write_client_manifest.js
+++ b/packages/kit/src/core/sync/write_client_manifest.js
@@ -48,23 +48,17 @@ export function write_client_manifest(manifest_data, output) {
 			.map((route) => {
 				if (route.page) {
 					const errors = route.page.errors.slice(1).map((n) => n ?? '');
-					const layouts = route.page.layouts.slice(1).map((n) => n ?? '');
+					const layouts = route.page.layouts.slice(1).map((n) => {
+						if (n == undefined) {
+							return '';
+						}
+						return get_node_id(manifest_data.nodes, n);
+					});
 
 					while (layouts.at(-1) === '') layouts.pop();
 					while (errors.at(-1) === '') errors.pop();
 
-					/** @type {import('types').PageNode | null} */
-					let current_node = route.leaf;
-
-					let uses_server_data = false;
-					while (current_node && !uses_server_data) {
-						uses_server_data = !!current_node?.server;
-						current_node = current_node?.parent ?? null;
-					}
-
-					// encode whether or not the route uses the server data
-					// using the ones' complement, to save space
-					const array = [`${uses_server_data ? '~' : ''}${route.page.leaf}`];
+					const array = [get_node_id(manifest_data.nodes, route.page.leaf)];
 
 					// only include non-root layout/error nodes if they exist
 					if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`);
@@ -77,6 +71,7 @@ export function write_client_manifest(manifest_data, output) {
 			.join(',\n\t\t')}
 	}`.replace(/^\t/gm, '');
 
+	// String representation of __GENERATED__/client-manifest.js
 	write_if_changed(
 		`${output}/client-manifest.js`,
 		trim(`
@@ -90,3 +85,13 @@ export function write_client_manifest(manifest_data, output) {
 		`)
 	);
 }
+
+/**
+ * Encode whether or not the route uses the server data
+ * using the ones' complement, to save space
+ * @param {import('types').PageNode[]} nodes
+ * @param {number} id
+ */
+function get_node_id(nodes, id) {
+	return `${nodes[id].server ? '~' : ''}${id}`;
+}
diff --git a/packages/kit/src/core/sync/write_root.js b/packages/kit/src/core/sync/write_root.js
index c1e160910..7bc1f7a79 100644
--- a/packages/kit/src/core/sync/write_root.js
+++ b/packages/kit/src/core/sync/write_root.js
@@ -21,7 +21,7 @@ export function write_root(manifest_data, output) {
 
 	let l = max_depth;
 
-	let pyramid = `<svelte:component this={components[${l}]} data={data_${l}}/>`;
+	let pyramid = `<svelte:component this={components[${l}]} data={data_${l}} {errors}/>`;
 
 	while (l--) {
 		pyramid = `
@@ -43,7 +43,7 @@ export function write_root(manifest_data, output) {
 			<!-- This file is generated by @sveltejs/kit — do not edit it! -->
 			<script>
 				import { setContext, afterUpdate, onMount } from 'svelte';
-				import { browser } from '$app/env';
+				import { browser } from '$app/environment';
 
 				// stores
 				export let stores;
diff --git a/packages/kit/src/runtime/app/env.js b/packages/kit/src/runtime/app/env.js
index 36ead7671..927c4af84 100644
--- a/packages/kit/src/runtime/app/env.js
+++ b/packages/kit/src/runtime/app/env.js
@@ -1,11 +1,1 @@
-/**
- * @type {import('$app/env').browser}
- */
-export const browser = !import.meta.env.SSR;
-
-/**
- * @type {import('$app/env').dev}
- */
-export const dev = __SVELTEKIT_DEV__;
-
-export { prerendering } from '../env.js';
+throw new Error('$app/env has been renamed to $app/environment');
diff --git a/packages/kit/src/runtime/app/environment.js b/packages/kit/src/runtime/app/environment.js
new file mode 100644
index 000000000..f7b96f8c1
--- /dev/null
+++ b/packages/kit/src/runtime/app/environment.js
@@ -0,0 +1,11 @@
+/**
+ * @type {import('$app/environment').browser}
+ */
+export const browser = !import.meta.env.SSR;
+
+/**
+ * @type {import('$app/environment').dev}
+ */
+export const dev = __SVELTEKIT_DEV__;
+
+export { prerendering } from '../env.js';
diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js
index 8f1e21984..0eb99f4df 100644
--- a/packages/kit/src/runtime/app/stores.js
+++ b/packages/kit/src/runtime/app/stores.js
@@ -1,5 +1,5 @@
 import { getContext } from 'svelte';
-import { browser } from './env.js';
+import { browser } from './environment.js';
 import { stores as browser_stores } from '../client/singletons.js';
 
 // TODO remove this (for 1.0? after 1.0?)
diff --git a/packages/kit/src/runtime/client/ambient.d.ts b/packages/kit/src/runtime/client/ambient.d.ts
index 74164d55c..ac822de8a 100644
--- a/packages/kit/src/runtime/client/ambient.d.ts
+++ b/packages/kit/src/runtime/client/ambient.d.ts
@@ -8,9 +8,9 @@ declare module '__GENERATED__/client-manifest.js' {
 
 	/**
 	 * A map of `[routeId: string]: [leaf, layouts, errors]` tuples, which
-	 * is parsed into an array of routes on startup. The numbers refer to the
-	 * indices in `nodes`. The route layout and error nodes are not referenced,
-	 * they are always number 0 and 1 and always apply.
+	 * is parsed into an array of routes on startup. The numbers refer to the indices in `nodes`.
+	 * If the number is negative, it means it does use a server load function and the complement is the node index.
+	 * The route layout and error nodes are not referenced, they are always number 0 and 1 and always apply.
 	 */
 	export const dictionary: Record<string, [leaf: number, layouts?: number[], errors?: number[]]>;
 
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index a3a06e72f..6849a433f 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -424,21 +424,37 @@ export function create_client({ target, base, trailing_slash }) {
 		};
 
 		let data = {};
-		let data_changed = false;
+		let data_changed = !page;
 		for (let i = 0; i < filtered.length; i += 1) {
-			data = { ...data, ...filtered[i].data };
+			const node = filtered[i];
+			data = { ...data, ...node.data };
+
 			// Only set props if the node actually updated. This prevents needless rerenders.
-			if (data_changed || !current.branch.some((node) => node === filtered[i])) {
+			if (data_changed || !current.branch.some((previous) => previous === node)) {
 				result.props[`data_${i}`] = data;
-				data_changed = true;
+				data_changed = data_changed || Object.keys(node.data ?? {}).length > 0;
 			}
 		}
+		if (!data_changed) {
+			// If nothing was added, and the object entries are the same length, this means
+			// that nothing was removed either and therefore the data is the same as the previous one.
+			// This would be more readable with a separate boolean but that would cost us some bytes.
+			data_changed = Object.keys(page.data).length !== Object.keys(data).length;
+		}
 
 		const page_changed =
 			!current.url || url.href !== current.url.href || current.error !== error || data_changed;
 
 		if (page_changed) {
-			result.props.page = { error, params, routeId, status, url, data };
+			result.props.page = {
+				error,
+				params,
+				routeId,
+				status,
+				url,
+				// The whole page store is updated, but this way the object reference stays the same
+				data: data_changed ? data : page.data
+			};
 
 			// TODO remove this for 1.0
 			/**
@@ -675,14 +691,13 @@ export function create_client({ target, base, trailing_slash }) {
 			params: Object.keys(params).filter((key) => current.params[key] !== params[key])
 		};
 
+		const loaders = [...layouts, leaf];
+
 		// preload modules to avoid waterfall, but handle rejections
 		// so they don't get reported to Sentry et al (we don't need
 		// to act on the failures at this point)
-		[...errors, ...layouts, leaf].forEach((loader) => loader?.().catch(() => {}));
-
-		const loaders = [...layouts, leaf];
-
-		// To avoid waterfalls when someone awaits a parent, compute as much as possible here already
+		errors.forEach((loader) => loader?.().catch(() => {}));
+		loaders.forEach((loader) => loader?.[1]().catch(() => {}));
 
 		/** @type {import('types').ServerData | null} */
 		let server_data = null;
@@ -690,15 +705,15 @@ export function create_client({ target, base, trailing_slash }) {
 		const invalid_server_nodes = loaders.reduce((acc, loader, i) => {
 			const previous = current.branch[i];
 			const invalid =
-				loader &&
-				(previous?.loader !== loader ||
+				!!loader?.[0] &&
+				(previous?.loader !== loader[1] ||
 					has_changed(changed, acc.some(Boolean), previous.server?.uses));
 
 			acc.push(invalid);
 			return acc;
 		}, /** @type {boolean[]} */ ([]));
 
-		if (route.uses_server_data && invalid_server_nodes.some(Boolean)) {
+		if (invalid_server_nodes.some(Boolean)) {
 			try {
 				const res = await native_fetch(
 					`${url.pathname}${url.pathname.endsWith('/') ? '' : '/'}__data.json${url.search}`,
@@ -741,7 +756,7 @@ export function create_client({ target, base, trailing_slash }) {
 			// re-use data from previous load if it's still valid
 			const valid =
 				can_reuse_server_data &&
-				loader === previous?.loader &&
+				loader[1] === previous?.loader &&
 				!has_changed(changed, parent_changed, previous.shared?.uses);
 			if (valid) return previous;
 
@@ -757,7 +772,7 @@ export function create_client({ target, base, trailing_slash }) {
 			}
 
 			return load_node({
-				loader,
+				loader: loader[1],
 				url,
 				params,
 				routeId: route.id,
@@ -801,11 +816,10 @@ export function create_client({ target, base, trailing_slash }) {
 
 							let j = i;
 							while (!branch[j]) j -= 1;
-
 							try {
 								error_loaded = {
-									node: await errors[i](),
-									loader: errors[i],
+									node: await /** @type {import('types').CSRPageNodeLoader } */ (errors[i])(),
+									loader: /** @type {import('types').CSRPageNodeLoader } */ (errors[i]),
 									data: {},
 									server: null,
 									shared: null
@@ -1087,7 +1101,7 @@ export function create_client({ target, base, trailing_slash }) {
 				: routes;
 
 			const promises = matching.map((r) => {
-				return Promise.all([...r.layouts, r.leaf].map((load) => load?.()));
+				return Promise.all([...r.layouts, r.leaf].map((load) => load?.[1]()));
 			});
 
 			await Promise.all(promises);
diff --git a/packages/kit/src/runtime/client/parse.js b/packages/kit/src/runtime/client/parse.js
index ec22a49b8..16e0c65ff 100644
--- a/packages/kit/src/runtime/client/parse.js
+++ b/packages/kit/src/runtime/client/parse.js
@@ -10,11 +10,6 @@ export function parse(nodes, dictionary, matchers) {
 	return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
 		const { pattern, names, types } = parse_route_id(id);
 
-		// whether or not the route uses the server data is
-		// encoded using the ones' complement, to save space
-		const uses_server_data = leaf < 0;
-		if (uses_server_data) leaf = ~leaf;
-
 		const route = {
 			id,
 			/** @param {string} path */
@@ -23,9 +18,8 @@ export function parse(nodes, dictionary, matchers) {
 				if (match) return exec(match, names, types, matchers);
 			},
 			errors: [1, ...(errors || [])].map((n) => nodes[n]),
-			layouts: [0, ...(layouts || [])].map((n) => nodes[n]),
-			leaf: nodes[leaf],
-			uses_server_data
+			layouts: [0, ...(layouts || [])].map(create_loader),
+			leaf: create_loader(leaf)
 		};
 
 		// bit of a hack, but ensures that layout/error node lists are the same
@@ -38,4 +32,16 @@ export function parse(nodes, dictionary, matchers) {
 
 		return route;
 	});
+
+	/**
+	 * @param {number} id
+	 * @returns {[boolean, import('types').CSRPageNodeLoader]}
+	 */
+	function create_loader(id) {
+		// whether or not the route uses the server data is
+		// encoded using the ones' complement, to save space
+		const uses_server_data = id < 0;
+		if (uses_server_data) id = ~id;
+		return [uses_server_data, nodes[id]];
+	}
 }
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index f80e5e375..ebde74cb4 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -74,10 +74,10 @@ declare namespace App {
 
 /**
  * ```ts
- * import { browser, dev, prerendering } from '$app/env';
+ * import { browser, dev, prerendering } from '$app/environment';
  * ```
  */
-declare module '$app/env' {
+declare module '$app/environment' {
 	/**
 	 * `true` if the app is running in the browser.
 	 */
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index 3fc11b752..2543bc14c 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -71,13 +71,16 @@ export interface CSRPageNode {
 
 export type CSRPageNodeLoader = () => Promise<CSRPageNode>;
 
+/**
+ * Definition of a client side route.
+ * The boolean in the tuples indicates whether the route has a server load.
+ */
 export type CSRRoute = {
 	id: string;
 	exec: (path: string) => undefined | Record<string, string>;
-	errors: CSRPageNodeLoader[];
-	layouts: CSRPageNodeLoader[];
-	leaf: CSRPageNodeLoader;
-	uses_server_data: boolean;
+	errors: Array<CSRPageNodeLoader | undefined>;
+	layouts: Array<[boolean, CSRPageNodeLoader] | undefined>;
+	leaf: [boolean, CSRPageNodeLoader];
 };
 
 export type GetParams = (match: RegExpExecArray) => Record<string, string>;
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js
index 8502249e6..59d18d09c 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -95,7 +95,7 @@ export async function read_file(dir, file) {
 			if (language === 'js') {
 				try {
 					if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) {
-						const params = parse_route_id(options.file || '+page.js')
+						const params = parse_route_id(options.file || `+page.${language}`)
 							.names.map((name) => `${name}: string`)
 							.join(', ');
 
@@ -115,7 +115,7 @@ export async function read_file(dir, file) {
 						} else {
 							source = source.replace(
 								/^(?!\/\/ @)/m,
-								`${injected}\n\n// @filename: index.js\n// ---cut---\n`
+								`${injected}\n\n// @filename: index.${language}\n// ---cut---\n`
 							);
 						}
 					}
diff --git a/sites/kit.svelte.dev/src/lib/search/Search.svelte b/sites/kit.svelte.dev/src/lib/search/Search.svelte
index 07a384352..0b5c47404 100644
--- a/sites/kit.svelte.dev/src/lib/search/Search.svelte
+++ b/sites/kit.svelte.dev/src/lib/search/Search.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { browser } from '$app/env';
+	import { browser } from '$app/environment';
 	import { searching, query } from './stores.js';
 
 	export let q = '';
diff --git a/sites/kit.svelte.dev/src/routes/+error.svelte b/sites/kit.svelte.dev/src/routes/+error.svelte
index 522caa3b0..72f5326cd 100644
--- a/sites/kit.svelte.dev/src/routes/+error.svelte
+++ b/sites/kit.svelte.dev/src/routes/+error.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { dev } from '$app/env';
+	import { dev } from '$app/environment';
 	import { page } from '$app/stores';
 
 	// we don't want to use <svelte:window bind:online> here, because we only care about the online
diff --git a/sites/kit.svelte.dev/src/routes/+layout.svelte b/sites/kit.svelte.dev/src/routes/+layout.svelte
index 5c7871032..075bc84db 100644
--- a/sites/kit.svelte.dev/src/routes/+layout.svelte
+++ b/sites/kit.svelte.dev/src/routes/+layout.svelte
@@ -1,6 +1,6 @@
 <script>
 	import '@sveltejs/site-kit/base.css';
-	import { browser } from '$app/env';
+	import { browser } from '$app/environment';
 	import { page, navigating } from '$app/stores';
 	import { Icon, Icons, Nav, NavItem, PreloadingIndicator, SkipLink } from '@sveltejs/site-kit';
 	import Search from '$lib/search/Search.svelte';


Test patch:
diff --git a/packages/kit/test/apps/amp/src/routes/valid/+page.svelte b/packages/kit/test/apps/amp/src/routes/valid/+page.svelte
index b8a04f492..17bca5973 100644
--- a/packages/kit/test/apps/amp/src/routes/valid/+page.svelte
+++ b/packages/kit/test/apps/amp/src/routes/valid/+page.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { browser, dev } from '$app/env';
+	import { browser, dev } from '$app/environment';
 
 	/** @type {import('./$types').PageData} */
 	export let data;
diff --git a/packages/kit/test/apps/basics/src/routes/+layout.js b/packages/kit/test/apps/basics/src/routes/+layout.js
index 0716988f4..b0527af4f 100644
--- a/packages/kit/test/apps/basics/src/routes/+layout.js
+++ b/packages/kit/test/apps/basics/src/routes/+layout.js
@@ -1,12 +1,6 @@
-import { error } from '@sveltejs/kit';
-
 /** @type {import('@sveltejs/kit').Load} */
-export async function load({ fetch, url }) {
-	if (url.pathname.startsWith('/errors/error-in-layout')) {
-		const res = await fetch('/errors/error-in-layout/non-existent');
-		throw error(res.status);
-	}
-
+export async function load() {
+	// Do NOT make this load function depend on something which would cause it to rerun
 	return {
 		foo: {
 			bar: 'Custom layout'
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 a57ef430c..bacb24019 100644
--- a/packages/kit/test/apps/basics/src/routes/+layout.server.js
+++ b/packages/kit/test/apps/basics/src/routes/+layout.server.js
@@ -1,4 +1,5 @@
 export async function load() {
+	// Do NOT make this load function depend on something which would cause it to rerun
 	return {
 		rootlayout: 'rootlayout'
 	};
diff --git a/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.svelte b/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.svelte
index 267cd0830..9ca9d4396 100644
--- a/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { browser } from '$app/env';
+	import { browser } from '$app/environment';
 
 	export let errors
 
diff --git a/packages/kit/test/apps/basics/src/routes/errors/+layout.js b/packages/kit/test/apps/basics/src/routes/errors/+layout.js
new file mode 100644
index 000000000..db7cc564d
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/+layout.js
@@ -0,0 +1,9 @@
+import { error } from '@sveltejs/kit';
+
+/** @type {import('@sveltejs/kit').Load} */
+export async function load({ fetch, url }) {
+	if (url.pathname.startsWith('/errors/error-in-layout')) {
+		const res = await fetch('/errors/error-in-layout/non-existent');
+		throw error(res.status);
+	}
+}
diff --git a/packages/kit/test/apps/basics/src/routes/errors/+layout.svelte b/packages/kit/test/apps/basics/src/routes/errors/+layout.svelte
new file mode 100644
index 000000000..0edcae4bf
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/+layout.svelte
@@ -0,0 +1,1 @@
+<slot />;
diff --git a/packages/kit/test/apps/basics/src/routes/errors/clientside/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/clientside/+page.svelte
index 37f41764d..45c779b91 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/clientside/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/errors/clientside/+page.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { browser } from '$app/env';
+	import { browser } from '$app/environment';
 
 	if (browser) {
 		throw new Error('Crashing now');
diff --git a/packages/kit/test/apps/basics/src/routes/errors/load-client/+page.js b/packages/kit/test/apps/basics/src/routes/errors/load-client/+page.js
index 30db34cea..50275c877 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/load-client/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/errors/load-client/+page.js
@@ -1,4 +1,4 @@
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 /** @type {import('@sveltejs/kit').Load} */
 export async function load() {
diff --git a/packages/kit/test/apps/basics/src/routes/errors/module-scope-client/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/module-scope-client/+page.svelte
index f87c7da0d..26f4a1ab3 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/module-scope-client/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/errors/module-scope-client/+page.svelte
@@ -1,5 +1,5 @@
 <script context="module">
-	import { browser } from '$app/env';
+	import { browser } from '$app/environment';
 
 	if (browser) {
 		throw new Error('Crashing now');
diff --git a/packages/kit/test/apps/basics/src/routes/errors/module-scope-server/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/module-scope-server/+page.svelte
index f499e4693..fb456e19b 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/module-scope-server/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/errors/module-scope-server/+page.svelte
@@ -1,5 +1,5 @@
 <script context="module">
-	import { dev } from '$app/env';
+	import { dev } from '$app/environment';
 
 	if (dev) { // can't throw in prod, the app won't start at all
 		throw new Error('Crashing now');
diff --git a/packages/kit/test/apps/basics/src/routes/errors/serverside/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/serverside/+page.svelte
index c036055e1..cb4f398c1 100644
--- a/packages/kit/test/apps/basics/src/routes/errors/serverside/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/errors/serverside/+page.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { dev } from '$app/env';
+	import { dev } from '$app/environment';
 
 	if (dev) { // can't throw in prod, the app won't start at all
 		throw new Error('Crashing now');
diff --git a/packages/kit/test/apps/basics/src/routes/load/+page.js b/packages/kit/test/apps/basics/src/routes/load/+page.js
index 069608981..937a72ade 100644
--- a/packages/kit/test/apps/basics/src/routes/load/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/load/+page.js
@@ -1,4 +1,4 @@
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 /** @type {import('@sveltejs/kit').Load} */
 export function load(pageContext) {
diff --git a/packages/kit/test/apps/basics/src/routes/load/change-detection/one/[x]/+page.js b/packages/kit/test/apps/basics/src/routes/load/change-detection/one/[x]/+page.js
index 30ea122f5..6375027b0 100644
--- a/packages/kit/test/apps/basics/src/routes/load/change-detection/one/[x]/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/load/change-detection/one/[x]/+page.js
@@ -1,4 +1,4 @@
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 let count = 0;
 
diff --git a/packages/kit/test/apps/basics/src/routes/load/fetch-request/+page.js b/packages/kit/test/apps/basics/src/routes/load/fetch-request/+page.js
index 5c13157f0..a3e6165b3 100644
--- a/packages/kit/test/apps/basics/src/routes/load/fetch-request/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/load/fetch-request/+page.js
@@ -1,4 +1,4 @@
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 /** @type {import('@sveltejs/kit').Load} */
 export async function load({ fetch }) {
diff --git a/packages/kit/test/apps/basics/src/routes/redirect-on-load/+page.js b/packages/kit/test/apps/basics/src/routes/redirect-on-load/+page.js
index 5cf9a8a68..be5f4e07f 100644
--- a/packages/kit/test/apps/basics/src/routes/redirect-on-load/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/redirect-on-load/+page.js
@@ -1,5 +1,5 @@
 import { redirect } from '@sveltejs/kit';
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 export async function load() {
 	if (browser) {
diff --git a/packages/kit/test/apps/basics/src/routes/routing/cancellation/a/+page.js b/packages/kit/test/apps/basics/src/routes/routing/cancellation/a/+page.js
index a5091c423..90c242efe 100644
--- a/packages/kit/test/apps/basics/src/routes/routing/cancellation/a/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/routing/cancellation/a/+page.js
@@ -1,4 +1,4 @@
-import { browser } from '$app/env';
+import { browser } from '$app/environment';
 
 /** @type {import('@sveltejs/kit').Load} */
 export async function load() {
diff --git a/packages/kit/test/apps/basics/src/routes/store/data/unchanged/+layout.svelte b/packages/kit/test/apps/basics/src/routes/store/data/unchanged/+layout.svelte
new file mode 100644
index 000000000..effe3150a
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/store/data/unchanged/+layout.svelte
@@ -0,0 +1,14 @@
+<script>
+	import { page } from '$app/stores';
+	let previous = $page.data;
+	let count = 0;
+	$: {
+		if (previous !== $page.data) {
+			count++;
+		}
+	}
+</script>
+
+<p>$page.data was updated {count} time(s)</p>
+<a href="/store/data/unchanged/a">a</a>
+<a href="/store/data/unchanged/b">b</a>
diff --git a/packages/kit/test/apps/basics/src/routes/store/data/unchanged/a/+page.svelte b/packages/kit/test/apps/basics/src/routes/store/data/unchanged/a/+page.svelte
new file mode 100644
index 000000000..98aa6d082
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/store/data/unchanged/a/+page.svelte
@@ -0,0 +1,1 @@
+Page A
\ No newline at end of file
diff --git a/packages/kit/test/apps/basics/src/routes/store/data/unchanged/b/+page.svelte b/packages/kit/test/apps/basics/src/routes/store/data/unchanged/b/+page.svelte
new file mode 100644
index 000000000..331b9e838
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/store/data/unchanged/b/+page.svelte
@@ -0,0 +1,1 @@
+Page B
\ No newline at end of file
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index a49bdca0a..03da4032d 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -598,11 +598,19 @@ test('Can use browser-only global on client-only page', async ({ page, read_erro
 	expect(read_errors('/no-ssr/browser-only-global')).toBe(undefined);
 });
 
-test('can use $app/stores from anywhere on client', async ({ page }) => {
-	await page.goto('/store/client-access');
-	await expect(page.locator('h1')).toHaveText('undefined');
-	await page.click('button');
-	await expect(page.locator('h1')).toHaveText('/store/client-access');
+test.describe('$app/stores', () => {
+	test('can use $app/stores from anywhere on client', async ({ page }) => {
+		await page.goto('/store/client-access');
+		await expect(page.locator('h1')).toHaveText('undefined');
+		await page.click('button');
+		await expect(page.locator('h1')).toHaveText('/store/client-access');
+	});
+
+	test('$page.data does not update if data is unchanged', async ({ page, app }) => {
+		await page.goto('/store/data/unchanged/a');
+		await app.goto('/store/data/unchanged/b');
+		await expect(page.locator('p')).toHaveText('$page.data was updated 0 time(s)');
+	});
 });
 
 test.describe.serial('Invalidation', () => {
diff --git a/packages/kit/test/apps/options/source/components/Message.svelte b/packages/kit/test/apps/options/source/components/Message.svelte
index e38144a08..03aaba873 100644
--- a/packages/kit/test/apps/options/source/components/Message.svelte
+++ b/packages/kit/test/apps/options/source/components/Message.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { browser, dev } from '$app/env';
+	import { browser, dev } from '$app/environment';
 </script>
 
 <p>Hello from the {browser ? 'client' : 'server'} in {dev ? 'dev' : 'prod'} mode!</p>
\ No newline at end of file
diff --git a/packages/kit/test/apps/options/source/pages/mode/+page.svelte b/packages/kit/test/apps/options/source/pages/mode/+page.svelte
index 466630884..6cde263d4 100644
--- a/packages/kit/test/apps/options/source/pages/mode/+page.svelte
+++ b/packages/kit/test/apps/options/source/pages/mode/+page.svelte
@@ -1,5 +1,5 @@
 <script>
-	import { dev } from '$app/env';
+	import { dev } from '$app/environment';
 
 	/** @type {import('./$types').PageData} */
 	export let data;
diff --git a/packages/kit/test/apps/writes/src/routes/double-mount/+page.svelte b/packages/kit/test/apps/writes/src/routes/double-mount/+page.svelte
index 6f94e183b..9fcd170c4 100644
--- a/packages/kit/test/apps/writes/src/routes/double-mount/+page.svelte
+++ b/packages/kit/test/apps/writes/src/routes/double-mount/+page.svelte
@@ -1,5 +1,5 @@
 <script context="module">
-	import { browser } from '$app/env';
+	import { browser } from '$app/environment';
 
 	if (browser) {
 		window.mounted = window.mounted || 0;
diff --git a/packages/kit/test/prerendering/basics/src/hooks.js b/packages/kit/test/prerendering/basics/src/hooks.js
index b8df4f86b..b27a99e8a 100644
--- a/packages/kit/test/prerendering/basics/src/hooks.js
+++ b/packages/kit/test/prerendering/basics/src/hooks.js
@@ -1,4 +1,4 @@
-import { prerendering } from '$app/env';
+import { prerendering } from '$app/environment';
 
 const initial_prerendering = prerendering;