diff --git a/.changeset/fix-trailing-slash-error-boundary.md b/.changeset/fix-trailing-slash-error-boundary.md new file mode 100644 index 000000000000..e460bd4a1d46 --- /dev/null +++ b/.changeset/fix-trailing-slash-error-boundary.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: preserve trailingSlash config when error boundary is above page node diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 0faada3d4b01..f02f582cbd39 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -635,9 +635,9 @@ async function initialize(result, target, hydrate) { * form?: Record | null; * }} opts */ -function get_navigation_result_from_branch({ url, params, branch, status, error, route, form }) { +function get_navigation_result_from_branch({ url, params, branch, status, error, route, form, trailing_slash }) { /** @type {import('types').TrailingSlash} */ - let slash = 'never'; + let slash = trailing_slash ?? 'never'; // if `paths.base === '/a/b/c`, then the root route is always `/a/b/c/`, regardless of // the `trailingSlash` route option, so that relative paths to JS and CSS work @@ -1197,13 +1197,21 @@ async function load_route({ id, invalidating, url, params, route, preload }) { const error_load = await load_nearest_error_page(i, branch, errors); if (error_load) { + // preserve trailingSlash config from the full branch so that + // slicing the branch for error handling doesn't lose it (#13516) + let trailing_slash; + for (const node of branch) { + if (node?.slash !== undefined) trailing_slash = node.slash; + } + return get_navigation_result_from_branch({ url, params, branch: branch.slice(0, error_load.idx).concat(error_load.node), status, error, - route + route, + trailing_slash }); } else { return await server_fallback(url, { id: route.id }, error, status); @@ -2401,13 +2409,21 @@ export async function set_nearest_error_page(error, status = 500) { const error_load = await load_nearest_error_page(current.branch.length, branch, route.errors); if (error_load) { + // preserve trailingSlash config from the full branch so that + // slicing the branch for error handling doesn't lose it (#13516) + let trailing_slash; + for (const node of branch) { + if (node?.slash !== undefined) trailing_slash = node.slash; + } + const navigation_result = get_navigation_result_from_branch({ url, params: current.params, branch: branch.slice(0, error_load.idx).concat(error_load.node), status, error, - route + route, + trailing_slash }); current = navigation_result.state; diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.js b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.js new file mode 100644 index 000000000000..55a4531d0d1d --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.js @@ -0,0 +1,7 @@ +import { error } from '@sveltejs/kit'; + +export const trailingSlash = 'always'; + +export function load() { + error(500, 'deliberate error'); +} diff --git a/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.svelte b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.svelte new file mode 100644 index 000000000000..742d64079091 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.svelte @@ -0,0 +1 @@ +

This should not be visible

diff --git a/packages/kit/test/apps/basics/test/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/cross-platform/client.test.js index 33afa996226e..a0dd7bfcad17 100644 --- a/packages/kit/test/apps/basics/test/cross-platform/client.test.js +++ b/packages/kit/test/apps/basics/test/cross-platform/client.test.js @@ -1154,6 +1154,13 @@ test.describe('Routing', () => { expect(new URL(page.url()).pathname).toBe('/routing/trailing-slash/never'); await expect(page.locator('p')).toHaveText('/routing/trailing-slash/never'); }); + + test('trailing slash is preserved when error boundary is above page config', async ({ page }) => { + await page.goto('/routing/trailing-slash/error-boundary/'); + // The page throws an error, which triggers the error boundary. + // The URL should still have the trailing slash from the page's trailingSlash='always' config. + expect(new URL(page.url()).pathname).toBe('/routing/trailing-slash/error-boundary/'); + }); }); test.describe('Shadow DOM', () => {