diff --git a/.changeset/curvy-students-march.md b/.changeset/curvy-students-march.md
new file mode 100644
index 000000000000..f413cf8e53c3
--- /dev/null
+++ b/.changeset/curvy-students-march.md
@@ -0,0 +1,10 @@
+---
+'@sveltejs/adapter-cloudflare': patch
+'@sveltejs/adapter-cloudflare-workers': patch
+'@sveltejs/adapter-netlify': patch
+'@sveltejs/adapter-node': patch
+'@sveltejs/adapter-vercel': patch
+'@sveltejs/kit': patch
+---
+
+[feat] Moved hooks.js initialization from Server.respond into Server.init
diff --git a/packages/adapter-cloudflare-workers/files/entry.js b/packages/adapter-cloudflare-workers/files/entry.js
index 0646c7acc380..fe1c45a246ea 100644
--- a/packages/adapter-cloudflare-workers/files/entry.js
+++ b/packages/adapter-cloudflare-workers/files/entry.js
@@ -15,7 +15,7 @@ export default {
 	 * @param {any} context
 	 */
 	async fetch(req, env, context) {
-		server.init({ env });
+		await server.init({ env });
 
 		const url = new URL(req.url);
 
diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js
index 20993ee11beb..a31267aef944 100644
--- a/packages/adapter-cloudflare/src/worker.js
+++ b/packages/adapter-cloudflare/src/worker.js
@@ -9,7 +9,7 @@ const prefix = `/${manifest.appDir}/`;
 /** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */
 const worker = {
 	async fetch(req, env, context) {
-		server.init({ env });
+		await server.init({ env });
 		// skip cache if "cache-control: no-cache" in request
 		let pragma = req.headers.get('cache-control') || '';
 		let res = !pragma.includes('no-cache') && (await Cache.lookup(req));
diff --git a/packages/adapter-netlify/src/serverless.js b/packages/adapter-netlify/src/serverless.js
index aec29eef562a..f898796b8902 100644
--- a/packages/adapter-netlify/src/serverless.js
+++ b/packages/adapter-netlify/src/serverless.js
@@ -9,11 +9,16 @@ import { split_headers } from './headers';
 export function init(manifest) {
 	const server = new Server(manifest);
 
-	server.init({
+	let init_promise = server.init({
 		env: process.env
 	});
 
 	return async (event, context) => {
+		if (init_promise !== null) {
+			await init_promise;
+			init_promise = null;
+		}
+
 		const response = await server.respond(to_request(event), {
 			platform: { context },
 			getClientAddress() {
diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js
index e5e67bfa3af9..d90bce52d996 100644
--- a/packages/adapter-node/src/handler.js
+++ b/packages/adapter-node/src/handler.js
@@ -11,7 +11,7 @@ import { env } from './env.js';
 /* global ENV_PREFIX */
 
 const server = new Server(manifest);
-server.init({ env: process.env });
+await server.init({ env: process.env });
 const origin = env('ORIGIN', undefined);
 const xff_depth = parseInt(env('XFF_DEPTH', '1'));
 
diff --git a/packages/adapter-node/tsconfig.json b/packages/adapter-node/tsconfig.json
index 89ca95cf92bd..63ea97316689 100644
--- a/packages/adapter-node/tsconfig.json
+++ b/packages/adapter-node/tsconfig.json
@@ -6,7 +6,8 @@
 		"noImplicitAny": true,
 		"allowSyntheticDefaultImports": true,
 		"moduleResolution": "node",
-		"module": "es2020",
+		"module": "es2022",
+		"target": "es2017",
 		"baseUrl": ".",
 		"paths": {
 			"@sveltejs/kit": ["../kit/types/index"]
diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/files/serverless.js
index 639275b9b7a1..c647fd8b1f9b 100644
--- a/packages/adapter-vercel/files/serverless.js
+++ b/packages/adapter-vercel/files/serverless.js
@@ -7,7 +7,7 @@ installPolyfills();
 
 const server = new Server(manifest);
 
-server.init({
+await server.init({
 	env: process.env
 });
 
diff --git a/packages/kit/src/core/prerender/prerender.js b/packages/kit/src/core/prerender/prerender.js
index a2cfbd79c118..410aa0e5bbc4 100644
--- a/packages/kit/src/core/prerender/prerender.js
+++ b/packages/kit/src/core/prerender/prerender.js
@@ -15,7 +15,7 @@ import { load_config } from '../config/index.js';
  * @typedef {import('types').Logger} Logger
  */
 
-const [, , client_out_dir, results_path, manifest_path, verbose] = process.argv;
+const [, , client_out_dir, results_path, manifest_path, verbose, env] = process.argv;
 
 prerender();
 
@@ -159,6 +159,7 @@ export async function prerender() {
 	});
 
 	const server = new Server(manifest);
+	await server.init({ env: JSON.parse(env) });
 
 	const error = normalise_error_handler(log, config);
 
diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js
index 793179b1c9f2..d2124e4f8308 100644
--- a/packages/kit/src/vite/build/build_server.js
+++ b/packages/kit/src/vite/build/build_server.js
@@ -84,7 +84,12 @@ export class Server {
 		};
 	}
 
-	init({ env }) {
+	/**
+	 * Take care: Some adapters may have to call \`Server.init\` per-request to set env vars,
+	 * so anything that shouldn't be rerun should be wrapped in an \`if\` block to make sure it hasn't
+	 * been done already.
+	 */
+	async init({ env }) {
 		const entries = Object.entries(env);
 
 		const prv = Object.fromEntries(entries.filter(([k]) => !k.startsWith('${
@@ -99,12 +104,6 @@ export class Server {
 		set_public_env(pub);
 
 		this.options.public_env = pub;
-	}
-
-	async respond(request, options = {}) {
-		if (!(request instanceof Request)) {
-			throw new Error('The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details');
-		}
 
 		if (!this.options.hooks) {
 			const module = await import(${s(hooks)});
@@ -114,6 +113,12 @@ export class Server {
 				externalFetch: module.externalFetch || fetch
 			};
 		}
+	}
+
+	async respond(request, options = {}) {
+		if (!(request instanceof Request)) {
+			throw new Error('The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details');
+		}
 
 		return respond(request, this.options, options);
 	}
diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js
index 7f6e74bc32fe..c6ecdb9ef889 100644
--- a/packages/kit/src/vite/index.js
+++ b/packages/kit/src/vite/index.js
@@ -411,7 +411,13 @@ function kit() {
 
 				const child = fork(
 					script,
-					[vite_config.build.outDir, results_path, manifest_path, '' + verbose],
+					[
+						vite_config.build.outDir,
+						results_path,
+						manifest_path,
+						'' + verbose,
+						JSON.stringify({ ...env.private, ...env.public })
+					],
 					{
 						stdio: 'inherit'
 					}
diff --git a/packages/kit/src/vite/preview/index.js b/packages/kit/src/vite/preview/index.js
index 56c47191360e..561d22cb1e36 100644
--- a/packages/kit/src/vite/preview/index.js
+++ b/packages/kit/src/vite/preview/index.js
@@ -45,7 +45,7 @@ export async function preview(vite, vite_config, svelte_config) {
 	});
 
 	const server = new Server(manifest);
-	server.init({
+	await server.init({
 		env: loadEnv(vite_config.mode, process.cwd(), '')
 	});
 
diff --git a/packages/kit/test/apps/basics/src/hooks.js b/packages/kit/test/apps/basics/src/hooks.js
index d6b9caa9d274..fe4b94752d81 100644
--- a/packages/kit/test/apps/basics/src/hooks.js
+++ b/packages/kit/test/apps/basics/src/hooks.js
@@ -1,6 +1,6 @@
 import fs from 'fs';
 import cookie from 'cookie';
-import { sequence } from '../../../../src/hooks';
+import { sequence } from '@sveltejs/kit/hooks';
 
 /** @type {import('@sveltejs/kit').HandleError} */
 export const handleError = ({ event, error }) => {
diff --git a/packages/kit/test/prerendering/basics/.env b/packages/kit/test/prerendering/basics/.env
new file mode 100644
index 000000000000..1597af1243ec
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/.env
@@ -0,0 +1,5 @@
+PRIVATE_STATIC="accessible to server-side code/replaced at build time"
+PRIVATE_DYNAMIC="accessible to server-side code/evaluated at run time"
+
+PUBLIC_STATIC="accessible anywhere/replaced at build time"
+PUBLIC_DYNAMIC="accessible anywhere/evaluated at run time"
diff --git a/packages/kit/test/prerendering/basics/.gitignore b/packages/kit/test/prerendering/basics/.gitignore
new file mode 100644
index 000000000000..1e18f275e97c
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/.gitignore
@@ -0,0 +1 @@
+!.env
\ No newline at end of file
diff --git a/packages/kit/test/prerendering/basics/src/routes/env/+page.server.js b/packages/kit/test/prerendering/basics/src/routes/env/+page.server.js
new file mode 100644
index 000000000000..617a8542fd21
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/env/+page.server.js
@@ -0,0 +1,9 @@
+import { PRIVATE_STATIC } from '$env/static/private';
+import { env } from '$env/dynamic/private';
+
+export function load() {
+	return {
+		PRIVATE_STATIC,
+		PRIVATE_DYNAMIC: env.PRIVATE_DYNAMIC
+	};
+}
diff --git a/packages/kit/test/prerendering/basics/src/routes/env/+page.svelte b/packages/kit/test/prerendering/basics/src/routes/env/+page.svelte
new file mode 100644
index 000000000000..4ee2f424da67
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/env/+page.svelte
@@ -0,0 +1,13 @@
+<script>
+	import { PUBLIC_STATIC } from '$env/static/public';
+	import { env } from '$env/dynamic/public';
+
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<p id="static-private">PRIVATE_STATIC: {data.PRIVATE_STATIC}</p>
+<p id="dynamic-private">PRIVATE_DYNAMIC: {data.PRIVATE_DYNAMIC}</p>
+
+<p id="static-public">PUBLIC_STATIC: {PUBLIC_STATIC}</p>
+<p id="dynamic-public">PUBLIC_DYNAMIC: {env.PUBLIC_DYNAMIC}</p>
diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js
index 280a862864a1..b38c6ab6503c 100644
--- a/packages/kit/test/prerendering/basics/test/test.js
+++ b/packages/kit/test/prerendering/basics/test/test.js
@@ -190,4 +190,19 @@ test('respects config.prerender.origin', () => {
 	assert.ok(content.includes('<h2>http://example.com</h2>'));
 });
 
+test('$env - includes environment variables', () => {
+	const content = read('env.html');
+
+	assert.match(
+		content,
+		/.*PRIVATE_STATIC: accessible to server-side code\/replaced at build time.*/gs
+	);
+	assert.match(
+		content,
+		/.*PRIVATE_DYNAMIC: accessible to server-side code\/evaluated at run time.*/gs
+	);
+	assert.match(content, /.*PUBLIC_STATIC: accessible anywhere\/replaced at build time.*/gs);
+	assert.match(content, /.*PUBLIC_DYNAMIC: accessible anywhere\/evaluated at run time.*/gs);
+});
+
 test.run();
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index a38968f52634..260cdfc00350 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -264,7 +264,7 @@ export interface ResolveOptions {
 
 export class Server {
 	constructor(manifest: SSRManifest);
-	init(options: ServerInitOptions): void;
+	init(options: ServerInitOptions): Promise<void>;
 	respond(request: Request, options: RequestOptions): Promise<Response>;
 }
 
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index 1d870c4ba64f..5e8376edbbce 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -94,7 +94,7 @@ export interface ImportNode {
 }
 
 export class InternalServer extends Server {
-	init(options: ServerInitOptions): void;
+	init(options: ServerInitOptions): Promise<void>;
 	respond(
 		request: Request,
 		options: RequestOptions & {