From fca32dfe964b23724de2ac7bc98fb82f15efbdce Mon Sep 17 00:00:00 2001 From: Daniel Bates Date: Thu, 19 Feb 2026 19:48:46 -0800 Subject: [PATCH 1/2] fix: preserve trailingSlash config when error boundary is above page node (#13516) When an error occurs and the branch is sliced to the nearest error boundary, the trailingSlash config from deeper nodes (e.g. the page) was lost because those nodes were removed from the branch. This caused the URL to be rewritten using the default 'never' setting, stripping trailing slashes even when trailingSlash='always' was configured on the page. The fix extracts the trailingSlash value from the full branch before slicing and passes it to get_navigation_result_from_branch as a fallback, so the config is preserved even when the branch is truncated for error handling. Co-Authored-By: Claude Opus 4.6 --- .../fix-trailing-slash-error-boundary.md | 5 ++++ packages/kit/src/runtime/client/client.js | 24 +++++++++++++++---- .../trailing-slash/error-boundary/+page.js | 7 ++++++ .../error-boundary/+page.svelte | 1 + .../basics/test/cross-platform/client.test.js | 9 +++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-trailing-slash-error-boundary.md create mode 100644 packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.js create mode 100644 packages/kit/test/apps/basics/src/routes/routing/trailing-slash/error-boundary/+page.svelte 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 1bb3f281cded..098ba06be5b2 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -631,9 +631,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 @@ -1193,13 +1193,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); @@ -2396,13 +2404,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..462636b8ee4d 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,15 @@ 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', () => { From cb958e41786566ff88723e754cf7ee548c380c01 Mon Sep 17 00:00:00 2001 From: Tee Ming Date: Fri, 20 Feb 2026 23:48:04 +0800 Subject: [PATCH 2/2] format --- .../kit/test/apps/basics/test/cross-platform/client.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 462636b8ee4d..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 @@ -1155,9 +1155,7 @@ test.describe('Routing', () => { await expect(page.locator('p')).toHaveText('/routing/trailing-slash/never'); }); - test('trailing slash is preserved when error boundary is above page config', async ({ - page - }) => { + 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.