Fast inner navigation for static sites
When trying to improve a web product experience and performance we usually encounter limitations. Sometimes we are not able to modify server configuration, build scripts or even some parts of the code that don't belong to our scope.
A good example of this is a site stored in GitHub pages, where there's not much to be done about server latency except moving to a paid solution.
In this context, I was trying to come up with something that would help navigation inside a static site to feel closer to instant view changes in single page applications.
Resource hints
The first thing a browser receives when users open a website is an html file, so any type of information we can give to it about what resources are going to be needed without actually blocking document parsing will be a huge win.
Anything we can do as the document parses (without blocking it) will be a huge win
This is where hints become crucial, they allow us to start connecting or fetching a resource ahead of time, but how do we do that?
link prefetching
Apart from loading stylesheets, the link
tag has another set of values for its rel
attribute to indicate other network related actions over a file or URL.
Browsers will take high or low priority actions when it detects these hints, in a similar way it starts to load an image file when it finds an img
tag.
For example, dns-prefetch will resolve the domain to get the resulting ip address, while preconnect does this plus the handshake and TLS negotiation.
<link rel="preconnect" href="//some-cloud-cdn.net/" crossorigin />
Both will save you some time when accessing external services and CDNs.
When instead of a domain we have a particular resource or route that could be needed in future navigations the prefetch value is used, as preload is for resources needed in the current page.
<link rel="prefetch" href="//your-site.com/next-route.html" />
If you would like to read more about resource hints or see more examples this oldie but goodie article by Luis Vieira is highly recommended.
Since we want to improve the inner navigation in a static site, we can combine prefetch with a little JavaScript to load possible upcoming routes in advance.
Prefetch on demand
To move forward with this idea we would need to append a link
element everytime the user hovers an anchor.
The element to append should look like this:
<link rel="prefetch" href="//your-project.com/the-future-route" />
First thing, let's collect all the anchors from the current page and turn them into an array to loop over them easier.
const anchors = document.getElementsByTagName('a');
const anchorsArray = [].slice.call(anchors);
// yay, we can use array methods now!
Next, let's create a method that adds a link
element to the document with the corresponding href
to prefetch.
const prefetchRoute = function () {
const link = document.createElement('link');
link.href = this.href;
link.rel = 'prefetch';
document.head.appendChild(link);
};
Keep in mind that once we use this method in an event listener the keyword this will point to the anchor element being hovered.
Now we attach this function to all anchors' events.
anchorsArray.map((anchor) => {
anchor.addEventListener('mouseover', prefetchRoute);
});
The interval of time between the user hovering and clicking a link might not be much, but it's enough to start ahead the connection to the resource and speed up the navigation at practically no cost.
Refinement
For the solution to work properly we should skip anchors with external links.
anchorsArray.map((anchor) => {
// only listen to hover when hosts match
if (anchor.host === document.location.host) {
anchor.addEventListener('mouseover', prefetchRoute);
}
});
We could also remove the listener after prefetching the route.
const prefetchRoute = function () {
const link = document.createElement('link');
link.href = this.href;
link.rel = 'prefetch';
document.head.appendChild(link);
// remove listener from anchor element
this.removeEventListener('mouseover', prefetchRoute);
};
This way we avoid injecting the same link
element several times.
What about mobile?
Since the mouseover
event won't be dispatched in mobile devices we could also attach the prefetchRoute
method to the touchstart
action.
I've personally bailed on doing it because there's a chance the device is not connected to wifi while navigating and I prefer not to silently consume users' data plan, even when they are just some kilobytes.
And single page applications?
If you are code splitting your application and using a bundler that allows named chunks like webpack does, you can prefetch that file to fasten dynamic routes.
Wrap-up
Even when you're in control of your server environment, using resource hints it's highly recommended. Take in count that for them to work you might need to serve your project on https.
This little trick is currently speeding up the experience in this site on desktop, so you can inspect the head
element or check the network tab on developer tools while passing your pointer over internal links to see it in action.
Kudos to Rich Harris from whom I stole this approach he used in Sapper.
Do you want me to write for your publication? Click here to contact me via email.