diff --git a/.changeset/few-paths-relate.md b/.changeset/few-paths-relate.md new file mode 100644 index 000000000000..31ccfee4ac49 --- /dev/null +++ b/.changeset/few-paths-relate.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: use correct relative asset paths when rendering an error page for a missing `__data.json` request diff --git a/packages/kit/src/runtime/app/paths/server.js b/packages/kit/src/runtime/app/paths/server.js index 123fe494525d..38ff6895f17c 100644 --- a/packages/kit/src/runtime/app/paths/server.js +++ b/packages/kit/src/runtime/app/paths/server.js @@ -1,6 +1,7 @@ import { base, assets, relative, initial_base } from './internal/server.js'; import { resolve_route, find_route } from '../../../utils/routing.js'; import { decode_pathname } from '../../../utils/url.js'; +import { add_data_suffix } from '../../pathname.js'; import { try_get_request_store } from '@sveltejs/kit/internal/server'; import { manifest } from '__sveltekit/server'; import { get_hooks } from '__SERVER__/internal.js'; @@ -26,7 +27,12 @@ export function resolve(id, params) { const store = try_get_request_store(); if (store && !store.state.prerendering?.fallback) { - const after_base = store.event.url.pathname.slice(initial_base.length); + // the relative path depth must reflect the URL the browser is actually at, which + // for a data request includes the `__data.json` suffix that was stripped during routing + const pathname = store.event.isDataRequest + ? add_data_suffix(store.event.url.pathname) + : store.event.url.pathname; + const after_base = pathname.slice(initial_base.length); const segments = after_base.split('/').slice(2); const prefix = segments.map(() => '..').join('/') || '.'; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 8b3a521977eb..0e58b63dcdb7 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -12,7 +12,7 @@ import { public_env } from '../../shared-server.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import { SCHEME } from '../../../utils/url.js'; import { create_server_routing_response, generate_route_object } from './server_routing.js'; -import { add_resolution_suffix } from '../../pathname.js'; +import { add_data_suffix, add_resolution_suffix } from '../../pathname.js'; import { try_get_request_store, with_request_store } from '@sveltejs/kit/internal/server'; import { text_encoder } from '../../utils.js'; import { count_non_ssi_comments, get_global_name, handle_error_and_jsonify } from '../utils.js'; @@ -114,7 +114,12 @@ export async function render_response({ // if appropriate, use relative paths for greater portability if (paths.relative) { if (!state.prerendering?.fallback) { - const segments = event.url.pathname.slice(paths.base.length).split('/').slice(2); + // the relative path depth must reflect the URL the browser is actually at, which + // for a data request includes the `__data.json` suffix that was stripped during routing + const pathname = event.isDataRequest + ? add_data_suffix(event.url.pathname) + : event.url.pathname; + const segments = pathname.slice(paths.base.length).split('/').slice(2); base = segments.map(() => '..').join('/') || '.'; diff --git a/packages/kit/test/apps/options-2/src/routes/+error.svelte b/packages/kit/test/apps/options-2/src/routes/+error.svelte new file mode 100644 index 000000000000..c8320ab59160 --- /dev/null +++ b/packages/kit/test/apps/options-2/src/routes/+error.svelte @@ -0,0 +1,9 @@ + + +

{page.status}

+ +

base: {resolve('/')}

+

assets: {asset('/')}

diff --git a/packages/kit/test/apps/options-2/test/test.js b/packages/kit/test/apps/options-2/test/test.js index 2c4541354be4..51b01c1caa30 100644 --- a/packages/kit/test/apps/options-2/test/test.js +++ b/packages/kit/test/apps/options-2/test/test.js @@ -39,6 +39,34 @@ test.describe('paths', () => { expect(await page.textContent('[data-testid="assets"]')).toBe(`assets: ${base}`); }); + test('uses correct relative paths when rendering an error page for a missing __data.json', async ({ + request + }) => { + // a non-existent page requested with the data suffix renders the error page. Its relative + // paths must be resolved against the requested URL (including the `__data.json` suffix), + // otherwise they end up one directory too shallow and fail to load the app's assets + const path = '/basepath/this/does/not/exist/__data.json'; + const response = await request.get(path); + expect(response.status()).toBe(404); + + const html = await response.text(); + + // `resolve('/')` and `asset('/')` (from `$app/paths`) + const relative_base = /base: ((?:\.\.\/)+) { await page.goto('/basepath'); expect(new URL(page.url()).pathname).toBe('/basepath/');