Skip to content

Comments

fix: preserve trailingSlash config when error boundary is above page node#15358

Open
danielalanbates wants to merge 4 commits intosveltejs:mainfrom
danielalanbates:fix/issue-13516
Open

fix: preserve trailingSlash config when error boundary is above page node#15358
danielalanbates wants to merge 4 commits intosveltejs:mainfrom
danielalanbates:fix/issue-13516

Conversation

@danielalanbates
Copy link

Summary

Fixes #13516

When an error occurs during client-side rendering and the branch is sliced to the nearest error boundary, the trailingSlash config from deeper nodes (e.g. the page's +page.js) is lost because those nodes are removed from the branch. This causes get_navigation_result_from_branch to fall back to the default 'never' setting, stripping the trailing slash from the URL even when trailingSlash = 'always' is configured on the page.

Changes

  • packages/kit/src/runtime/client/client.js: Added a trailing_slash parameter to get_navigation_result_from_branch that serves as a fallback when the (sliced) branch contains no nodes with a slash property. At both call sites where the branch is sliced for error handling, the trailingSlash value is extracted from the full branch before slicing and passed through.

  • Test: Added a test case that navigates to a page with trailingSlash = 'always' that throws an error, verifying the URL retains the trailing slash when the error boundary (defined at a higher level) catches the error.

How it works

The root cause is that branch.slice(0, error_load.idx).concat(error_load.node) removes the page node that contained the trailingSlash config. The fix preserves this config by:

  1. Scanning the full branch for slash before slicing
  2. Passing the found value as trailing_slash to get_navigation_result_from_branch
  3. Using it as the initial value of slash (instead of 'never'), which can still be overridden by any slash property found in the sliced branch

This PR was created with the assistance of Claude Opus 4.6 by Anthropic. Happy to make any adjustments!

…node (sveltejs#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 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 20, 2026

🦋 Changeset detected

Latest commit: b30c5bd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

* }} 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 }) {
Copy link
Contributor

@vercel vercel bot Feb 20, 2026

Choose a reason for hiding this comment

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

JSDoc type definition for get_navigation_result_from_branch is missing the trailing_slash parameter, causing TypeScript errors

Fix on Vercel

@benmccann
Copy link
Member

It looks like you need to run pnpm format and fix some failing tests. E.g.:

    1) [chromium-dev] › test/cross-platform/client.test.js:1158:2 › Routing › trailing slash is preserved when error boundary is above page config 
  
      Error: expect(received).toBe(expected) // Object.is equality
  
      Expected: "/routing/trailing-slash/error-boundary/"
      Received: "/routing/trailing-slash/error-boundary"
  
        1160 | 		// The page throws an error, which triggers the error boundary.
        1161 | 		// The URL should still have the trailing slash from the page's trailingSlash='always' config.
      > 1162 | 		expect(new URL(page.url()).pathname).toBe('/routing/trailing-slash/error-boundary/');
             | 		                                     ^
        1163 | 	});
        1164 | });
        1165 |
          at /home/runner/work/kit/kit/packages/kit/test/apps/basics/test/cross-platform/client.test.js:1162:40

Comment on lines +1196 to +1201
// 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;
}
Copy link
Member

Choose a reason for hiding this comment

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

It's probably better to create a util for this so we don't repeat it so many times. It's currently repeated 3 times in the module.

Maybe something like:

/**
 * @param {(import('./types.js').BranchNode | undefined)[]} branch
 * @param {URL} url
 * @returns {import('types').TrailingSlash}
 */
function get_trailing_slash(branch, url) {
	// 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
	if (base && (url.pathname === base || url.pathname === base + '/')) {
		return 'always';
	}

	return branch.reduce((value, node) => {
		return node?.slash ?? value;
	}, /** @type {import('types').TrailingSlash} */ ('never'));
}

Copy link
Member

@teemingc teemingc left a comment

Choose a reason for hiding this comment

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

This is almost the right solution but we need one more change. The trailing slash info for the node isn't returned when the load function fails. Ideally, we'd still keep track of it even if the load function fails. We need to make changes so that we get

slash: node.universal?.trailingSlash ?? server_data_node?.slash
even if the load throws

return get_navigation_result_from_branch({
url,
params,
branch: [root_layout, root_error],
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to compute the trailing slash option here too so that it respects the root layout's trailing slash option

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

trailingSlash=always ignored on client render when error boundary is defined before/above node config

4 participants