What if I told you there's a way to dramatically speed up page transitions just by adding a library? With zero or few code changes? And it's overlooked by the contemporary blogosphere?
Demo time! https://mitranim.com/simple-pjax/
Who benefits from this?
As you might have guessed, we're going to exploit clientside routing with history.pushState
. It's usually considered a domain of client-rendered SPA, but what a mistake that is!
When you think about it, the status quo of content delivery on the web is insane. We're forcing visitors to make dozens of network connections and execute massive amounts of JavaScript on each page load on the same site.
👎 Typical page transition
With pushstate routing, we can do better.
👍 Page transition with pjax
The idea is dead simple. Say a user navigates from page A to page B on your site. Instead of a full page reload, fetch B by ajax, replace A, and update the URL using history.pushState
. This technique has been termed pjax
.
Here's a super naive example to illustrate the point. (DON'T COPY THIS, SEE BELOW)
document.addEventListener('click', function(event) { // Find a clicked <a>, if any. const anchor = event.target do { if (anchor instanceof HTMLAnchorElement) break } while (anchor = anchor.parentElement) if (!anchor) return event.preventDefault() const xhr = new XMLHttpRequest() xhr.onload = function() { if (xhr.status < 200 || xhr.status > 299) return xhr.onerror() // Update the URL to match the clicked link. history.pushState(null, '', anchor.href) // Replace the old document with the new content. document.body = xhr.responseXML.body window.scrollTo(0, 0) } xhr.onerror = xhr.onabort = xhr.ontimeout = function() { // Ensure a normal page transition. history.pushState(null, '', anchor.href) location.reload() } xhr.open('GET', anchor.href) // This will automatically parse the response as XML on the fly. xhr.responseType = 'document' xhr.send(null) })
I have fashioned this into a simple, fully automatic library. Just drop it into your site and enjoy the benefits. Feedback and contributions are welcome! If you happen to find a better implementation, I'd be happy to hear about it.
Despite the simplicity, the benefits are stunning. This gives your multi-page website most of the advantages enjoyed by SPA. The browser gets to keep the same JavaScript runtime and all downloaded assets, including images, fonts, stylesheets, etc. This dramatically improves page load times, particularly on poor connections such as mobile networks. This also lets you maintain a persistent websocket connection while the user navigates your server-rendered multi-page app!
Also, I can't overstate how wasteful it is to execute all scripts on each new page load, which is typical for most websites. I just checked Wired and the total execution time of all scripts was 480 ms before ads kicked in. Each new page reruns all scripts. Using pjax, you can eliminate this waste, keeping your website more responsive and saving the visitors' CPU cycles and battery life.
You need to watch out for code that modifies the DOM on page load. Most websites have this in the form of analytics and UI widgets. When transitioning to a new page, that code must be re-executed to modify the new document body.
Before a transition, you'll need to perform teardown like unmounting React components or destroying jQuery plugins. Do that in a document-level simple-pjax-before-transition
event listener.
After a transition, you'll need to run the same setup as on the first page load. Do that in a document-level simple-pjax-after-transition
event listener.
simple-pjax
also reruns any inline scripts found in the new document body, which makes it compatible out-of-the-box with common analytics snippets.
You'll also need to take special care of widget libraries with a fragile DOM lifecycle, like Angular or Polymer. They break when document body is replaced. Notably, React is perfectly compatible; just make sure to unmount all components before replacing the body.
Pjax has been around for a few years. There are a few implementations floating around, like the eponymous jQuery plugin. Pjax is baked into Ruby on Rails and YUI. Many sites use it in one form or another.
Why isn't pjax more popular? Maybe because people overengineer it. The libraries I've seen tend to focus on downloading partials (HTML snippets). They require you to micromanage the markup, and some need a special server configuration. I think these people have missed the point. The biggest benefit is keeping the browsing session alive, and this can be achieved with zero configuration or thought. For most sites, this is enough, and additional effort is usually not worth it. Is this wrong? You tell me!
Let's use this technique to improve the web!
This blog currently doesn't support comments. Write to me via contacts.