Basic idea - navigating between pages is slow, because:
- browser waits for network response
- it is expensive to redraw the whole page from scratch. Much faster would be to replace
innerHTML
of the document. At least this is common belief, but there are nuances to that.
History
First solution appeared in 2010. See defunkt/jquery-pjax
Around 2015 "olympic torch" was taken over by turbolinks. Which was part of Rails stack and later replaced by Turbo Drive.
Around the same time there were also:
- MoOx/pjax
- falsandtru/pjax-api
- barba, 2016
- swup, 2017
In 2018 Google jump in on that boat with quicklink
Note: later doesn't mean better, because development didn't stop at the release date. For example, swup
recently released v4.
Is it still alive?
Yes.
One would think that with SPA trend (started around 2015) nobody would care about this kind of solutions, but static website still popular. For example Hugo, Astro and others
The devil is in the details
I put all those libraries in one category but they are actually different and use different assumptions. For example,
- PJAX uses History API and AJAX (or fetch). It takes over browser navigation mechanism
- quicklink on the other side bothers itself only with prefetching/prerendering
Browser history
So we have two main approaches:
- don't take over browser navigation
- pros:
- easy to integrate
- cons:
- animations are not possible
- state of the page reseted, like selected tabs, collapsed menu, scroll position in sidebar
- examples:
quicklink
, @astrojs/prefetch, astro-hover-prefetch, swup preload
- take over browser navigation
- pros:
- hard to integrate
- cons:
- animations are possible
- state of the page can be preserved but needs additional work
- examples:
pjax
,barba
,swup
, astro-spa
Adaptability
Besides browser navigation we need to take into account:
- we don't want to prefetch all the links
- quicklink uses Intersection Observer to detect links within the viewport
- Other approach is to do this on hover, focus, tap
- Some libraries allows to specify which links to prefetch
- Or we can statistically guess
- we don't want it to do while browser is busy doing something else. You don't want to degrade user experience
- quicklink uses requestIdleCallback
- Making additional requests on mobile devices may cost user additional money
- quicklink uses
navigator.connection.effectiveType
to detect slow networks, andnavigator.connection.saveData
to detect data-saver mode
- quicklink uses
Related functionality
Main subject is to speed up navigation between pages, but there are related functionalities:
-
animate page navigations, like in
swup
andbarba
- replace only part of the page, for example to filter list or table, like in Turbo Frames, swup fragment, astro view transitions
- show page preview on hover, like in Obsidian. See also previewbox and astro-link-preview
- highlight section if navigated with anchor, like in StakOverflow
I mainly concern myself with static web sites, but in the context of dynamic websites there is so called "HTML over wire" approach:
- Elixir Phoenix LiveView
- Ruby Turbo
- PHP Laravel livewire
Complexity with browser history
Basic implementation can look like this
- Add global click event listener
- on click you fetch remote content
- extract element that needs to be replaced (for example
main
) - replace content with
target.innerHTML = response
- Push new item to history with
history.pushState(state, "", url);
But you need to take care of:
- removing event handlers from old HTML
- attaching event handlers to new HTML
- replacing title, maybe announce it to screen reader
- replacing styles
- replacing scripts
- maybe provide a hook so that it would be possible to integrate analytics
- scrolling to top of the page
- listen to history for back navigations
- maybe resolve relative links
- it doesn't preserve state of scroll or tabs
It is an error-prone approach.
To be continued...
While I was doing the research on the subject I stumbled upon:
I will need more time to finish the research