19
loading...
This website collects cookies to deliver better user experience
href
attribute of your link (an a
or area
element). Client-side navigation refers to the practice of using JavaScript to control page transitions without a full reload, which usually results in a snappier user experience. Despite its popularity, many implementations are broken or lacking: history manipulation, scroll restoration, ctrl + click / cmd + click / right click behavior, loading state handling etc. are often buggy or non-existent. In many cases this actually makes the user experience worse than classic navigation by breaking user expectations.#hash
part of the URL and the window.onhashchange
event. Since it is normally used for scrolling to a specific section of a document, a hash-only navigation doesn't cause a full page reload. Developers took advantage of this to implement client-side navigation with history (back/forward buttons) support. In early 2010s, the history API support landed in popular browsers which allowed using real URL paths instead of hashes.history.pushState
or history.replaceState
is called. This means that a client-side library solution must provide its own navigation function, because, barring horrible hacks, it has no way to be notified when the user of the library calls these methods.Link
component that renders an a
element with an attached an onclick
handler, or 2) by attaching a global onclick
handler to the body
element.a
element.a
elements in such cases. SvelteKit uses this second approach. Opting out of client-side navigation is still possible: We can interpret, for instance, the presence of a rel="external"
attribute as a signal for letting the browser handle the navigation. The downside of the second approach is that one has to be careful about event handling order. If you attach an onclick
handler to the a
element, it will run after the global one which may not be what you want. You have to use { capture: true }
if you want to alter the click behavior of a link.LinkContainer
component that captures the onclick
events of the a
elements that it contains. It solves the “pre-rendered HTML that we don't control” problem while remaining fairly explicit.Link
component is still useful for styling active (or pending) links differently, a nice feature to have in navigation menus for instance.onclick
events, it is important to know when to leave the handling to the browser. The following cases should be considered:preventDefault()
called before our handler?a
element have a href
attribute at all?a
element have a target
attribute whose value is not _self
?a
element have a download
attribute?location.href
does not match currently rendered page contents. This may cause mismatches in links with relative URLs: Say you are on page /foo
and you initiate a client-side navigation to /foo/bar
. If there is a link whose href
is baz
(a relative link), it will be pointing to /foo/baz
instead of /baz
while the navigation is underway. One way to solve this problem is to have a base
element in the document head whose href
property is always kept in sync with the currently rendered location.history.scrollRestoration
which can be set to manual
or auto
. The former is the default value and means that the browser will not restore the scroll position. You might think that you can set it to auto
and be done with it. Unfortunately, this is not the case if you have to support asynchronous rendering like we discussed above. The scroll position needs to be restored after the new page has been rendered in its entirety. Consider this scenario: You're at the bottom of a page that has contents that don't fit in the viewport (/long
). You navigate to a page that does fit (/short
). When you click back, automatic scroll restoration will try to scroll to the original position but unless you are able to render /long
synchronously, it will fail because the contents of /short
will be showing while /long
is still loading and they fit the page so there's nowhere to scroll to.history.scrollRestoration
. A decent client-side navigation solution must set it to manual
and handle scroll restoration manually, after the new page has been fully rendered. One way to approach this is to assign a unique ID to each location, keeping track of it in history.state
and use it as a sessionStorage
key to store the scroll position.#hash
links.onbeforeunload
event. When set up correctly, it will show a confirmation dialog before navigating away from the current page. This is useful to remind the user that they might lose unsaved data.location.href
is already updated by the time the onpopstate
event is called. This means that we don't know if we should go back or forward to cancel the navigation. To solve this, we can use history.state
to keep track of the current location's history index and compare it to the last rendered index in order to compute a delta value to pass to history.go
for “taking back” the navigation attempt. Then we can show a dialog box to ask the user if they really want to leave the page. If the answer is no, we stop, if the answer is yes, we redo the navigation using history.go(-delta)
.onbeforeunload
fallback in case the user clicks on a hard link or simply closes the tab.knave
, a framework-agnostic client-side navigation library in order to address all of these challenges once and for all. The knave-react
package contains its React bindings. PRs that implement bindings for other frameworks are welcome.