Progressively Enhanced Single Page App in under 1kb

terabaud profile image Lea Rosema Updated on ・2 min read

I was wondering how to add SPA functionality to a statically generated site that just uses HTML,CSS and no JS.

I've used Eleventy and I wanted to add as little overhead as possible. Also, it should follow the principles of Progressive Enhancement (basically, it should keep working without the JS being required).

I ended up with 851 bytes of JavaScript unminified:

(function () {
  'use strict';
  var $ = function (sel, con) {
    return (con || document).querySelector(sel);
  var nav = $('.nav');
  if (!nav) {
    console.warn('no navigation found.');
  nav.addEventListener('click', function (evt) {
    var el = evt.target;
    var spa = el.getAttribute('data-spa');
    if (spa) {
      var href = el.getAttribute('href');
      var container = $(spa);
      var xhr = new XMLHttpRequest();
      xhr.onload = function () {
        var d = this.responseXML;
        var dTitle = d.title || '';
        var dContainer = $(spa, d);
        container.innerHTML = (dContainer && dContainer.innerHTML) || '';
        history.pushState({}, dTitle, href);
      xhr.open('GET', href);
      xhr.responseType = 'document';
Enter fullscreen mode Exit fullscreen mode

This snippet looks for the first navigation element with the class name nav in the and listens for click events. If the clicked target is a link with an extra data-spa attribute, then the link will be treated as a Single Page Application link.

In this case, a whole page reload is prevented via evt.preventDefault().

Instead of navigating to the page, the HTML is fetched via an AJAX request. The value of the data-spa attribute contains a selector that specifies the content element on the page (eg main).

The content of the content container is then replaced by the content container inside the HTML document fetched by AJAX.

Finally, the history.pushState() updates the HTML in the browser.


Because I can ;).

One use case: if you have some kind of interactive component inside your page, you may want to allow the user to navigate through your page without reloading that component.

Ugh. It looks like jQuery.

Ok, I admit I loved jQuery in the 2010's, but there are no dependencies to jQuery. It uses $ as an abbreviation to document.querySelector.

Why so IE11-friendly legacy code?

I thought it was nice to even also support IE. And throwing ES2020 onto it doesn't significantly reduce the file size.



Posted on by:

terabaud profile

Lea Rosema


Frontend Developer by day, funstuff developer princess by night. Working at SinnerSchrader. she/her


Editor guide

Hi Lea, I'm a big fan of using custom vanilla js for adding small functionalities to websites. I was a bit curious about the double negative in the spa condition. Is there a specific reason for it? It seems simpler to use:

if(spa) {
    //rest of code.

Have a good day!


You're super right, that's simpler :) the double !! is just coercion to boolean and not needed. Sometimes I like to do that if I need some kind of boolean variable like isSpa but it's not needed here :)

Thanks for the heads up!