Repository: sveltejs/kit (js)

Issue:
`errors` exported by `+page.svelte` is only available on the server and not on the client
### Describe the bug

Cannot access the `errors` object in the client returned by actions (`+page.server.js`). 

### Reproduction

https://github.com/pilcrowOnPaper/sveltekit-action-issue

Description and steps are described in the repo README

### Logs

 (Browser console)
```shell
<Root> was created without expected prop 'errors'
```


### System Info

```shell
System:
    OS: macOS 12.5
    CPU: (8) x64 Intel(R) Core(TM) i7-1060NG7 CPU @ 1.20GHz
    Memory: 870.41 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
Binaries:
    Node: 18.7.0 - ~/.nvm/versions/node/v18.7.0/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 8.15.0 - ~/.nvm/versions/node/v18.7.0/bin/npm
Browsers:
    Chrome: 98.0.4758.102
    Firefox: 103.0.2
    Safari: 15.6
npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.64
    @sveltejs/kit: next => 1.0.0-next.408
    svelte: ^3.44.0 => 3.49.0
    vite: ^3.0.0 => 3.0.7
```

Tested with: Safari, Firefox, Chrome


### Severity

blocking an upgrade

### Additional Information

_No response_

Pull request description:
[fix] hydrate validation errors
Fixes #5906

### 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/angry-peaches-warn.md b/.changeset/angry-peaches-warn.md
new file mode 100644
index 000000000..7569870fa
--- /dev/null
+++ b/.changeset/angry-peaches-warn.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Hydration validation errors
diff --git a/.changeset/good-mails-crash.md b/.changeset/good-mails-crash.md
new file mode 100644
index 000000000..dc6077647
--- /dev/null
+++ b/.changeset/good-mails-crash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Fix generated path extension for `AwaitedProperties`
diff --git a/.changeset/lazy-owls-run.md b/.changeset/lazy-owls-run.md
new file mode 100644
index 000000000..8dc9830c5
--- /dev/null
+++ b/.changeset/lazy-owls-run.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] prevent `Content-Length` header from being incorrectly inherited by requests made from `load`'s `fetch` during SSR
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 4336b4b0e..87cc1785a 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -331,6 +331,7 @@
     "gold-trains-dream",
     "good-beans-invite",
     "good-keys-argue",
+    "good-mails-crash",
     "good-monkeys-reflect",
     "good-peaches-roll",
     "gorgeous-beans-glow",
diff --git a/.changeset/slow-spiders-agree.md b/.changeset/slow-spiders-agree.md
new file mode 100644
index 000000000..48d869966
--- /dev/null
+++ b/.changeset/slow-spiders-agree.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Provide helpful error message on invalid named layout reference
diff --git a/documentation/docs/04-advanced-routing.md b/documentation/docs/04-advanced-routing.md
index 4541b15f8..8c82a8be0 100644
--- a/documentation/docs/04-advanced-routing.md
+++ b/documentation/docs/04-advanced-routing.md
@@ -144,6 +144,8 @@ Some parts of your app might need something other than the default layout. For t
 <h1>I am inside +layout-foo</h1>
 ```
 
+> Named layout should only be referenced from Svelte files
+
 Named layouts are very powerful, but it can take a minute to get your head round them. Don't worry if this doesn't make sense all at once.
 
 #### Scoping
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index d1e3ce8ac..fc8dc6696 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/kit
 
+## 1.0.0-next.410
+
+### Patch Changes
+
+- Fix generated path extension for `AwaitedProperties` ([#5917](https://github.com/sveltejs/kit/pull/5917))
+
 ## 1.0.0-next.409
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index df1360a66..477f69469 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.409",
+	"version": "1.0.0-next.410",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
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 9234a5e3f..3cba3936d 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -67,14 +67,7 @@ export default function create_manifest_data({
 
 			if (file[0] !== '+') return; // not a route file
 
-			const item = analyze(file, config.extensions, config.kit.moduleExtensions);
-
-			if (!item) {
-				throw new Error(
-					`Files and directories prefixed with + are reserved (saw ${project_relative})`
-				);
-			}
-
+			const item = analyze(project_relative, file, config.extensions, config.kit.moduleExtensions);
 			const id = segments.join('/');
 
 			if (/\]\[/.test(id)) {
@@ -275,19 +268,22 @@ export default function create_manifest_data({
 }
 
 /**
+ * @param {string} project_relative
  * @param {string} file
  * @param {string[]} component_extensions
  * @param {string[]} module_extensions
- * @returns {import('./types').RouteFile | null}
+ * @returns {import('./types').RouteFile}
  */
-function analyze(file, component_extensions, module_extensions) {
+function analyze(project_relative, file, component_extensions, module_extensions) {
 	const component_extension = component_extensions.find((ext) => file.endsWith(ext));
 	if (component_extension) {
 		const name = file.slice(0, -component_extension.length);
 		const pattern =
 			/^\+(?:(page(?:@([a-zA-Z0-9_-]+))?)|(layout(?:-([a-zA-Z0-9_-]+))?(?:@([a-zA-Z0-9_-]+))?)|(error))$/;
 		const match = pattern.exec(name);
-		if (!match) return null;
+		if (!match) {
+			throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
+		}
 
 		return {
 			kind: 'component',
@@ -302,21 +298,29 @@ function analyze(file, component_extensions, module_extensions) {
 	const module_extension = module_extensions.find((ext) => file.endsWith(ext));
 	if (module_extension) {
 		const name = file.slice(0, -module_extension.length);
-		const pattern = /^\+(?:(server)|(page(\.server)?)|(layout(?:-([a-zA-Z0-9_-]+))?(\.server)?))$/;
+		const pattern =
+			/^\+(?:(server)|(page(?:@([a-zA-Z0-9_-]+))?(\.server)?)|(layout(?:-([a-zA-Z0-9_-]+))?(?:@([a-zA-Z0-9_-]+))?(\.server)?))$/;
 		const match = pattern.exec(name);
-		if (!match) return null;
+		if (!match) {
+			throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
+		} else if (match[3] || match[7]) {
+			throw new Error(
+				// prettier-ignore
+				`Only Svelte files can reference named layouts. Remove '@${match[3] || match[7]}' from ${file} (at ${project_relative})`
+			);
+		}
 
-		const kind = !!(match[1] || match[3] || match[6]) ? 'server' : 'shared';
+		const kind = !!(match[1] || match[4] || match[8]) ? 'server' : 'shared';
 
 		return {
 			kind,
 			is_page: !!match[2],
-			is_layout: !!match[4],
-			declares_layout: match[5]
+			is_layout: !!match[5],
+			declares_layout: match[6]
 		};
 	}
 
-	return null;
+	throw new Error(`Files and directories prefixed with + are reserved (saw ${project_relative})`);
 }
 
 /**
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
index a841e923d..f60559ec4 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
@@ -566,6 +566,13 @@ test('errors on layout named default', () => {
 	);
 });
 
+test('errors on invalid named layout reference', () => {
+	assert.throws(
+		() => create('samples/invalid-named-layout-reference'),
+		/Only Svelte files can reference named layouts. Remove '@named' from \+page@named.js \(at samples\/invalid-named-layout-reference\/\+page@named.js\)/
+	);
+});
+
 test('errors on duplicate layout definition', () => {
 	assert.throws(
 		() => create('samples/duplicate-layout'),
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/invalid-named-layout-reference/+layout-named.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/invalid-named-layout-reference/+layout-named.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/invalid-named-layout-reference/+page@named.js b/packages/kit/src/core/sync/create_manifest_data/test/samples/invalid-named-layout-reference/+page@named.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/invalid-named-layout-reference/+page@named.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/invalid-named-layout-reference/+page@named.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/src/core/sync/write_types.js b/packages/kit/src/core/sync/write_types.js
index 0d1c3ad71..b9aca6951 100644
--- a/packages/kit/src/core/sync/write_types.js
+++ b/packages/kit/src/core/sync/write_types.js
@@ -431,17 +431,12 @@ function process_node(ts, node, outdir, params, groups) {
 	function get_data_type(file_path, method, fallback, proxy) {
 		if (proxy) {
 			if (proxy.exports.includes(method)) {
-				if (proxy.modified) {
-					const basename = path.basename(file_path);
-					return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('./proxy${basename}').${method}>>>`;
-				} else {
-					// If the file wasn't tweaked, we can use the return type of the original file.
-					// The advantage is that type updates are reflected without saving.
-					return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import("${path_to_original(
-						outdir,
-						file_path
-					)}").${method}>>>`;
-				}
+				// If the file wasn't tweaked, we can use the return type of the original file.
+				// The advantage is that type updates are reflected without saving.
+				const from = proxy.modified
+					? `./proxy${replace_ext_with_js(path.basename(file_path))}`
+					: path_to_original(outdir, file_path);
+				return `Kit.AwaitedProperties<Awaited<ReturnType<typeof import('${from}').${method}>>>`;
 			} else {
 				return fallback;
 			}
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 77bad23c5..491f1ef4b 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -389,6 +389,7 @@ export function create_client({ target, session, base, trailing_slash }) {
 	 *   status: number;
 	 *   error: HttpError | Error | null;
 	 *   routeId: string | null;
+	 *   validation_errors?: string | undefined;
 	 * }} opts
 	 */
 	async function get_navigation_result_from_branch({
@@ -397,7 +398,8 @@ export function create_client({ target, session, base, trailing_slash }) {
 		branch,
 		status,
 		error,
-		routeId
+		routeId,
+		validation_errors
 	}) {
 		const filtered = /** @type {import('./types').BranchNode[] } */ (branch.filter(Boolean));
 
@@ -412,7 +414,8 @@ export function create_client({ target, session, base, trailing_slash }) {
 				session_id
 			},
 			props: {
-				components: filtered.map((branch_node) => branch_node.node.component)
+				components: filtered.map((branch_node) => branch_node.node.component),
+				errors: validation_errors
 			}
 		};
 
@@ -1211,8 +1214,16 @@ export function create_client({ target, session, base, trailing_slash }) {
 			let result;
 
 			try {
-				const script = document.querySelector(`script[sveltekit\\:data-type="server_data"]`);
-				const server_data = script?.textContent ? JSON.parse(script.textContent) : [];
+				/**
+				 * @param {string} type
+				 * @param {any} fallback
+				 */
+				const parse = (type, fallback) => {
+					const script = document.querySelector(`script[sveltekit\\:data-type="${type}"]`);
+					return script?.textContent ? JSON.parse(script.textContent) : fallback;
+				};
+				const server_data = parse('server_data', []);
+				const validation_errors = parse('validation_errors', undefined);
 
 				const branch_promises = node_ids.map(async (n, i) => {
 					return load_node({
@@ -1243,6 +1254,7 @@ export function create_client({ target, session, base, trailing_slash }) {
 								error.message
 						  )
 						: error,
+					validation_errors,
 					routeId
 				});
 			} catch (e) {
diff --git a/packages/kit/src/runtime/server/page/fetch.js b/packages/kit/src/runtime/server/page/fetch.js
index 6790844b7..5ae3b3206 100644
--- a/packages/kit/src/runtime/server/page/fetch.js
+++ b/packages/kit/src/runtime/server/page/fetch.js
@@ -52,6 +52,7 @@ export function create_fetch({ event, options, state, route }) {
 			if (
 				key !== 'authorization' &&
 				key !== 'connection' &&
+				key !== 'content-length' &&
 				key !== 'cookie' &&
 				key !== 'host' &&
 				key !== 'if-none-match' &&
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index b3b237fce..ea4969da9 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -266,6 +266,12 @@ export async function render_response({
 			);
 		}
 
+		if (validation_errors) {
+			serialized_data.push(
+				render_json_payload_script({ type: 'validation_errors' }, validation_errors)
+			);
+		}
+
 		body += `\n\t${serialized_data.join('\n\t')}`;
 	}
 
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index 4a5e001e1..059e8e078 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -141,7 +141,8 @@ export interface PageData {
 
 export type PayloadScriptAttributes =
 	| { type: 'data'; url: string; body?: string }
-	| { type: 'server_data' };
+	| { type: 'server_data' }
+	| { type: 'validation_errors' };
 
 export interface PrerenderDependency {
 	response: Response;


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.server.js b/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.server.js
new file mode 100644
index 000000000..29b3ed36c
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.server.js
@@ -0,0 +1,7 @@
+export const POST = async () => {
+	return {
+		errors: {
+			message: 'an error occurred'
+		}
+	};
+};
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
new file mode 100644
index 000000000..267cd0830
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/actions/form-errors/+page.svelte
@@ -0,0 +1,18 @@
+<script>
+	import { browser } from '$app/env';
+
+	export let errors
+
+	const hydrated_error_message = browser ? 'hydrated: ' + errors?.message : '';
+</script>
+
+<form method="post">
+	<button type="submit">Submit</button>
+</form>
+
+<p class="server">{errors?.message}</p>
+
+<!-- needs to be like this else the selector is found too soon (before hydration) -->
+{#if hydrated_error_message}
+	<p class="client">{hydrated_error_message}</p>
+{/if}
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index cb624a0ef..b5dad6f1f 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -1717,3 +1717,14 @@ test.describe('XSS', () => {
 		);
 	});
 });
+
+test.describe('Actions', () => {
+	test('Error props are returned', async ({ page, javaScriptEnabled }) => {
+		await page.goto('/actions/form-errors');
+		await page.click('button');
+		expect(await page.textContent('p.server')).toBe('an error occurred');
+		if (javaScriptEnabled) {
+			expect(await page.textContent('p.client')).toBe('hydrated: an error occurred');
+		}
+	});
+});