VT's dev log
All posts

Published

Last updated

Make static websites feel like mobile apps

Navigation in a single-page-app is second to none, because there is no need to refresh the page. This article describes how a normal website can achieve the same feel with just one html-tag

web

Mobile apps raised the bar of UX in terms of navigation between pages. Unlike traditional websites that reload the entire page when navigating, apps loads content dynamically, creating seamless navigation. But what if you could replicate this seamless app navigation on a regular website — and do it with minimal code?

Chrome's experimental Speculation Rules API makes this possible by telling the browser to prerender pages referenced in <a> tags (behaviour is customisable). This behavior is configured by a <script> tag with a specific json format. This article will inject the script tag at runtime, because we want a fallback behavior if the browser doesn't support this experimental API. Here's an example implementation of the Speculation Rules API:

function preloadWithSpeculation() {
  const tag = document.createElement("script");

  tag.type = "speculationrules";

  const rules = {
    prerender: [
      {
        source: "document",
        eagerness: "moderate",
        where: {
          href_matches: "/*",
        },
      },
    ],
  };
  tag.textContent = JSON.stringify(rules);
  document.head.append(tag);
}

This function injects a script tag that looks like this:

<script type="speculationrules">
  {
    "prerender": [
      {
        "source": "document",
        "eagerness": "moderate",
        "where": {
          "href_matches": "/*"
        }
      }
    ]
  }
</script>

With this configuration Chrome will prerender any page that's referenced with the href attribute in <a>, that's starts with / (pages on this website). This might not be what you want but this is what fitted my use-case.

Next we need a implementation that can preload links without using any experimental API. In this article we will use <link rel="preload"> tags, which is a standard way to preload (but not render) the webpage specified by the href attribute. We will instruct the browser to preload a page on hover of any a tag:

function preloadWithLinks() {
  document.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll("a").forEach((link) => {
      // isAHrefValid: function to determine if link
      //   is non-external and to an actual webpage (not "mailto:" for example).
      if (isAHrefValid(link)) return;
      link.addEventListener(
        "mouseover",
        () => {
          const href = link.getAttribute("href");
          if (!href) return;
          const prefetch = document.createElement("link");
          prefetch.rel = "prefetch";
          prefetch.href = href;
          document.head.appendChild(prefetch);
        },
        { once: true },
      );
    });
  });
}

Now we need a way to choose implementation based on if the browser supports the Speculative Rules API:

if (
  HTMLScriptElement.supports &&
  HTMLScriptElement.supports("speculationrules")
) {
  preloadWithSpeculation();
} else {
  preloadWithLinks();
}

And we are done!

NOTE

These snippets where taken from this websites preload-loader.ts.