This is a reproduction repo to illustrate a navigation issue in SvelteKit >=2.21.3. It is still present in 2.50.1.
If your app's HTML[^1], specifies a HTML <base href="/"> element, then SvelteKit-managed navigations to paths with a hash in them will behave unexpectedly on non-root (/) routes.
This issue exists since this PR https://github.com/sveltejs/kit/pull/10856 was merged, which released as part of SvelteKit 2.21.3.
If I have <a href="/other-page#myhash">My link</a> on the / root page, then I expect that link to navigate to the /other-page#myhash route, since this is normal browser behavior.
Given the above setup, clicking this link will:
/other-page#myhash using client-side routing/#myhashWe end up on /#myhash and not on /other-page#myhash, and a navigation "flash" is seen due to the hard navigation after the client-side navigation.
(tested on the latest Firefox & Helium (Chromium-based) on macOS)
In SvelteKit, if you navigate to another SvelteKit page using a path like /other-page#myhash in a <a href="/other-page#myhash"> or in a goto('/other-page#myhash') call, then SvelteKit by default internally calls window.location.replace('#myhash') in the reset_focus() function, see
this line in client/client.js.
Here, it specifies only the hash extracted from the path as a relative URL. This implicitly assumes that no <base href="..."> element is set, because otherwise, behavior will not be aligned with normal browser behavior, since the originally intended path is ignored by the .replace() call.
Why it works without a <base> element? If no <base href="..." /> element is set, then the relative #myhash will be relative to the current path. Since the client-side history-API-based routing already occurred, we will already be on the target page, and this will just add or replace the hash in the URL without navigating, which is expected.
If a <base href="/${MY_BASE}/"> is set (like in this reproduction), then this take will you to /${MY_BASE}/#myhash, which is unexpected (see below).
In almost all cases where a <base> element is set, the SvelteKit-managed navigation behavior does not align with normal browser behavior expected from anchor links.
Consider a HTML page with <base href="/subfolder/" />.
Two examples:
<a href="/other-page#myhash"> here will still take you to /other-page#myhash, despite the base element. SvelteKit would take you to /subfolder/#myhash.<a href="other-page#myhash"> would take you to /subfolder/other-page#myhash, while SvelteKit would take you to /subfolder/#myhash.The conclusion is: window.location.replace('#myhash') ignores all paths, absolute or relative, specified in the link's href, and this should probably not be the case.
Don't use a <base> element. It was in my project for 3 years, before I even worked on it, and I don't know at the moment why it's there.
However, there might be a legitimate use cases for <base> elements, and the above I think shows that they can't be safely used in SvelteKit at the moment.
Navigating to a link with the keepFocus setting set to true bypasses the reset_focus() function, which in turn bypasses this issue. Alternatively, adding the data-sveltekit-keepfocus attribute to an element on the page with the link achieves the same effect.
Would there be any downside to resolving the URL path, including hash, as it was specified, in the location.replace call, instead of just using the hash?
I didn't fully try to understand SvelteKit's navigation logic yet, nor the reasoning behind the PR that introduced this issue. I could definitely be missing something with this suggestion.
[^1]: This reproduction puts a <base> element in app.html, but I assume that if it appears through statically-rendered HTML, or even through dynamically-rendered HTML in the SvelteKit component tree, it will still be a problem.