Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/few-paths-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: use correct relative asset paths when rendering an error page for a missing `__data.json` request
8 changes: 7 additions & 1 deletion packages/kit/src/runtime/app/paths/server.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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('/') || '.';

Expand Down
9 changes: 7 additions & 2 deletions packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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('/') || '.';

Expand Down
9 changes: 9 additions & 0 deletions packages/kit/test/apps/options-2/src/routes/+error.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { page } from '$app/state';
import { resolve, asset } from '$app/paths';
</script>

<h1 data-testid="error-status">{page.status}</h1>

<p data-testid="base">base: {resolve('/')}</p>
<p data-testid="assets">assets: {asset('/')}</p>
28 changes: 28 additions & 0 deletions packages/kit/test/apps/options-2/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: ((?:\.\.\/)+)</.exec(html)?.[1];
const relative_assets = /assets: ((?:\.\.\/)+)</.exec(html)?.[1];
// the base path expression embedded in the bootstrap script
const base_expression = /new URL\("((?:\.\.\/?)+)", location\)/.exec(html)?.[1];

expect(relative_base).toBeTruthy();
expect(relative_assets).toBeTruthy();
expect(base_expression).toBeTruthy();

const base_url = `http://localhost${path}`;
expect(new URL(/** @type {string} */ (relative_base), base_url).pathname).toBe('/basepath/');
expect(new URL(/** @type {string} */ (relative_assets), base_url).pathname).toBe('/basepath/');
expect(new URL(/** @type {string} */ (base_expression), base_url).pathname).toBe('/basepath/');
});

test('serves /basepath with trailing slash always', async ({ page }) => {
await page.goto('/basepath');
expect(new URL(page.url()).pathname).toBe('/basepath/');
Expand Down
Loading