Repository: sveltejs/kit (JavaScript)

Issue:
fetch passed to load function in +page.server.js implements CORS
### Describe the bug

Even though it will only ever run on the server, the fetch implementation that is passed to load in +page.server.js is checking for CORS, behaving as if it were called in a browser.

I assume this might be an oversight from when +page.server routes were first introduced, since obviously you'd very much want this behavior in non-server page.js load function, but it doesn't really make any sense in the new environment.

I hope I didn't overlook an issue where this was discussed already and kept for technical reasons, but even then we'd probably want this documented.

### Reproduction

https://stackblitz.com/edit/sveltejs-kit-template-default-52u2qj?file=src%2Froutes%2F%2Blayout.svelte,src%2Froutes%2F%2Bpage.svelte,src%2Froutes%2F%2Bpage.server.js&terminal=dev

### Logs

_No response_

### System Info

```Shell
stackblitz
```


### Severity

annoyance

The global fetch correctly doesn't implement CORS, so one can simply use that. However it is a bit confusing getting a CORS error from a request you make on the server.

### Additional Information

_No response_


Discussion:
Some discussion here for additional context: https://discord.com/channels/457912077277855764/1035096174014771231/1035096174014771231



Patch:
diff --git a/.changeset/wet-news-tie.md b/.changeset/wet-news-tie.md
new file mode 100644
index 000000000000..1d072a376e1e
--- /dev/null
+++ b/.changeset/wet-news-tie.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Only simulate CORS errors for shared load functions
diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js
index 1632db5b80a2..3b8ebefae719 100644
--- a/packages/kit/src/runtime/server/fetch.js
+++ b/packages/kit/src/runtime/server/fetch.js
@@ -62,17 +62,6 @@ export function create_fetch({ event, options, state, get_cookie_header }) {
 							statusText: response.statusText,
 							headers: response.headers
 						});
-					} else {
-						if (url.origin !== event.url.origin) {
-							const acao = response.headers.get('access-control-allow-origin');
-							if (!acao || (acao !== event.url.origin && acao !== '*')) {
-								throw new Error(
-									`CORS error: ${
-										acao ? 'Incorrect' : 'No'
-									} 'Access-Control-Allow-Origin' header is present on the requested resource`
-								);
-							}
-						}
 					}
 
 					return response;
diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js
index 73d25dbd02c9..dec13492ca0e 100644
--- a/packages/kit/src/runtime/server/page/load_data.js
+++ b/packages/kit/src/runtime/server/page/load_data.js
@@ -109,9 +109,24 @@ export async function load_data({
 			/** @type {import('types').PrerenderDependency} */
 			let dependency;
 
-			if (same_origin && state.prerendering) {
-				dependency = { response, body: null };
-				state.prerendering.dependencies.set(url.pathname, dependency);
+			if (same_origin) {
+				if (state.prerendering) {
+					dependency = { response, body: null };
+					state.prerendering.dependencies.set(url.pathname, dependency);
+				}
+			} else {
+				// simulate CORS errors server-side for consistency with client-side behaviour
+				const mode = input instanceof Request ? input.mode : init?.mode ?? 'cors';
+				if (mode !== 'no-cors') {
+					const acao = response.headers.get('access-control-allow-origin');
+					if (!acao || (acao !== event.url.origin && acao !== '*')) {
+						throw new Error(
+							`CORS error: ${
+								acao ? 'Incorrect' : 'No'
+							} 'Access-Control-Allow-Origin' header is present on the requested resource`
+						);
+					}
+				}
 			}
 
 			const proxy = new Proxy(response, {


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js
new file mode 100644
index 000000000000..9fbd10aed9bf
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js
@@ -0,0 +1,8 @@
+/** @type {import('./$types').PageLoad} */
+export async function load({ fetch, url }) {
+	const res = await fetch(`http://localhost:${url.searchParams.get('port')}`);
+
+	return {
+		text: await res.text()
+	};
+}
diff --git a/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.svelte
new file mode 100644
index 000000000000..a5063216b089
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.svelte
@@ -0,0 +1,6 @@
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>{@html data.text}</h1>
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index b352c5b404a3..523cdb4dad8c 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -936,7 +936,10 @@ test.describe('Load', () => {
 		expect(await page.textContent('h1')).toBe('true');
 	});
 
-	test('CORS errors are simulated server-side', async ({ page, read_errors }) => {
+	test('CORS errors are simulated server-side for shared load functions', async ({
+		page,
+		read_errors
+	}) => {
 		const { port, close } = await start_server(async (req, res) => {
 			res.end('hello');
 		});
@@ -952,6 +955,17 @@ test.describe('Load', () => {
 
 		await close();
 	});
+
+	test('CORS errors are skipped for server-only load functions', async ({ page }) => {
+		const { port, close } = await start_server(async (req, res) => {
+			res.end('hello');
+		});
+
+		await page.goto(`/load/cors/server-only?port=${port}`);
+		expect(await page.textContent('h1')).toBe('hello');
+
+		await close();
+	});
 });
 
 test.describe('Nested layouts', () => {