Skip to content
Open
5 changes: 5 additions & 0 deletions .changeset/honest-cobras-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': major
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'@sveltejs/kit': major
'@sveltejs/kit': patch

It should only be major for a breaking change (of a previously valid behaviour?). However, I don't think anyone would purposely try to have the load fetch trigger the SvelteKit server response if it's requesting an entirely different path

---

fix: `fetch` not working when URL is same host but different than `paths.base`
8 changes: 6 additions & 2 deletions packages/kit/src/runtime/server/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
request.headers.delete('origin');
}

if (url.origin !== event.url.origin) {
const decoded = decodeURIComponent(url.pathname);

if (
url.origin !== event.url.origin ||
(paths.base && decoded !== paths.base && !decoded.startsWith(`${paths.base}/`))
) {
// Allow cookie passthrough for "credentials: same-origin" and "credentials: include"
// if SvelteKit is serving my.domain.com:
// - domain.com WILL NOT receive cookies
Expand All @@ -77,7 +82,6 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
// handle fetch requests for static assets. e.g. prebaked data, etc.
// we need to support everything the browser's fetch supports
const prefix = paths.assets || paths.base;
const decoded = decodeURIComponent(url.pathname);
const filename = (
decoded.startsWith(prefix) ? decoded.slice(prefix.length) : decoded
).slice(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
fetch('/request-abort', { headers: { accept: 'application/json' } }).then(
async (r) => (result = await r.json())
);
}, 100);
}, 50);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this reduced?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test was flaky, it was failing randomly in the CI and locally, reducing the abort signal timing helped.

Should I revert this change?

}

onMount(test_abort);
Expand Down
3 changes: 3 additions & 0 deletions packages/kit/test/apps/options/source/pages/+server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function GET() {
return new Response('root');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export async function load({ fetch }) {
const response = await fetch('/not-base-path/');
return {
fetchUrl: response.url,
fetchResponse: await response.text()
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
/** @type {import('./$types').PageProps} */
const { data } = $props();
</script>

<p data-testid="fetch-url">{data.fetchUrl}</p>
<p data-testid="fetch-response">{data.fetchResponse}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export async function load({ fetch }) {
// fetch to root with trailing slash
const response1 = await fetch('/');
// fetch to root without trailing slash
const response2 = await fetch('');
// fetch to root with custom base path with trailing slash
const response3 = await fetch('/path-base/');
// fetch to root with custom base path without trailing slash
const response4 = await fetch('/path-base');

return {
fetchUrl1: response1.url,
fetchUrl2: response2.url,
fetchUrl3: response3.url,
fetchUrl4: response4.url,
fetchResponse1: await response1.text(),
fetchResponse2: await response2.text(),
fetchResponse3: await response3.text(),
fetchRedirect4: response4.headers.get('location')
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script>
/** @type {import('./$types').PageProps} */
const { data } = $props();
</script>

<h2>Fetch URLs</h2>

<dl>
<dt>fetch1-url</dt>
<dd data-testid="fetch1-url">{data.fetchUrl1}</dd>
<dt>fetch2-url</dt>
<dd data-testid="fetch2-url">{data.fetchUrl2}</dd>
<dt>fetch3-url</dt>
<dd data-testid="fetch3-url">{data.fetchUrl3}</dd>
<dt>fetch4-url</dt>
<dd data-testid="fetch4-url">{data.fetchUrl4}</dd>
</dl>

<h2>Fetch Responses</h2>
<dl>
<dt>fetch1-response</dt>
<dd data-testid="fetch1-response">{data.fetchResponse1}</dd>
<dt>fetch2-response</dt>
<dd data-testid="fetch2-response">{data.fetchResponse2}</dd>
<dt>fetch3-response</dt>
<dd data-testid="fetch3-response">{data.fetchResponse3}</dd>
<dt>fetch4-redirect</dt>
<dd data-testid="fetch4-redirect">{data.fetchRedirect4}</dd>
</dl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function GET() {
return new Response('relative');
}
35 changes: 35 additions & 0 deletions packages/kit/test/apps/options/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,39 @@ test.describe('Async', () => {
await expect(page.locator('h1', { hasText: 'Page B' })).toBeVisible();
expect(logs).toEqual(['mounted', 'navigated']);
});

test.describe('Fetch', () => {
test('fetch outside base path succeeds', async ({ page, baseURL }) => {
await page.goto('/path-base/fetch/link-outside-base/');
expect(await page.locator('[data-testid="fetch-url"]').textContent()).toContain(
`${baseURL}/not-base-path/`
);
expect(await page.locator('[data-testid="fetch-response"]').textContent()).toContain(
'did you mean to visit'
);
});

test('fetch to root succeeds', async ({ page, baseURL }) => {
await page.goto('/path-base/fetch/link-root/');
// fetch to root with trailing slash
expect(await page.locator('[data-testid="fetch1-url"]').textContent()).toContain(
`${baseURL}/`
);
expect(await page.locator('[data-testid="fetch1-response"]').textContent()).toContain('root');

// fetch to root without trailing slash should be relative
expect(await page.locator('[data-testid="fetch2-url"]').textContent()).toBeFalsy();
expect(await page.locator('[data-testid="fetch2-response"]').textContent()).toBe('relative');

// fetch to root with custom base path with trailing slash
expect(await page.locator('[data-testid="fetch3-url"]').textContent()).toBeFalsy();
expect(await page.locator('[data-testid="fetch3-response"]').textContent()).toBe('root');

// fetch to root with custom base path without trailing slash
expect(await page.locator('[data-testid="fetch4-url"]').textContent()).toBeFalsy();
expect(await page.locator('[data-testid="fetch4-redirect"]').textContent()).toBe(
'/path-base/'
);
});
});
});
Loading