Two ways to prefetch: <link> tags and HTTP headers

Demián Renzulli
Demián Renzulli

In this codelab, you'll implement prefetching in two ways: with <link rel="prefetch"> and with HTTP Link header.

The sample app is a website that has a promotional landing page with a special discount for the shop's best selling t-shirt. Since the landing page links to a single product, it's safe to assume that a high percentage of users will navigate to the product details page. This makes the product page a great candidate to prefetch on the landing page.

Measure performance

First establish the baseline performance:

  1. Click Remix to Edit to make the project editable.
  2. To preview the site, press View App. Then press Fullscreen fullscreen.
  3. Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
  4. Click the Network tab.

  5. In the Throttling drop-down list, select Fast 3G to simulate a slow connection type.

  6. To load the product page, click Buy now in the sample app.

The product-details.html page takes about 600 ms to load:

Network panel showing load times for product-details.html

To improve navigation, insert a prefetch tag in the landing page to prefetch the product-details.html page:

  • Add the following <link> element to the head of the views/index.html file:
<!doctype html>
  <html>
    <head>
       <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet">

      <link rel="prefetch" href="/product-details.html" as="document">
      ...
</head>

The as attribute is optional but recommended; it helps the browser set the right headers and determine whether the resource is already in the cache. Example values for this attribute include: document, script, style, font, image, and others.

To verify that prefetching is working:

  1. To preview the site, press View App. Then press Fullscreen fullscreen.
  2. Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
  3. Click the Network tab.

  4. In the Throttling drop-down list, select Fast 3G to simulate a slow connection type.

  5. Clear the Disable cache checkbox.

  6. Reload the app.

Now when the landing page loads, the product-details.html page loads too, but at the lowest priority:

Network panel showing product-details.html prefetched.

The page is kept in the HTTP cache for five minutes, after which the normal Cache-Control rules for the document apply. In this case, product-details.html has a cache-control header with a value of public, max-age=0, which means that the page is kept for a total of five minutes.

Reevaluate performance

  1. Reload the app.
  2. To load the product page, click Buy now in the sample app.

Take a look at the Network panel. There are two differences compared to the initial network trace:

  • The Size column shows "prefetch cache", which means this resource was retrieved from the browser's cache rather than the network.
  • The Time column shows that the time it takes for the document to load is now about 10 ms.

This is approximately a 98% reduction compared to the previous version, which took about 600 ms.

Network panel showing product-details.html retrieved from prefetch cache.

Extra credit: Use prefetch as a progressive enhancement

Prefetching is best implemented as a progressive enhancement for the users who are browsing on fast connections. You can use the Network Information API to check the network conditions and based on that dynamically inject prefetch tags. That way, you can minimize data consumption and save costs for users on slow or expensive data plans.

To implement adaptive prefetching, first remove the <link rel="prefetch"> tag from views/index.html:

<!doctype html>
  <html>
    <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet">
       <link rel="prefetch" href="/product-details.html" as="document">
       ...
    </head>

Then add the following code to public/script.js to declare a function that dynamically injects the prefetch tag when the user is on a fast connection:

function injectLinkPrefetchIn4g(url) {
    if (window.navigator.connection.effectiveType === '4g') {
        //generate link prefetch tag
        const linkTag = document.createElement('link');
        linkTag.rel = 'prefetch';
        linkTag.href = url;
        linkTag.as = 'document';

        //inject tag in the head of the document
        document.head.appendChild(linkTag);
    }
}

The function works as follows:

  • It checks the effectiveType property of the Network Information API to determine if the user is on a 4G (or faster) connection.
  • If that condition is fulfilled, it generates a <link> tag with prefetch as the type of hint, passes the URL that will be prefetched in the href attribute, and indicates that the resource is an HTML document in the as attribute.
  • Finally, it injects the script dynamically in the head of the page.

Next add script.js to views/index.html, just before the closing </body> tag:

<body>
      ...
      <script src="/script.js"></script>
</body>

Requesting script.js at the end of the page ensures that it will be loaded and executed after the page is parsed and loaded.

To make sure that the prefetching doesn't interfere with critical resources for the current page, add the following code snippet to call injectLinkPrefetchIn4g() on the window.load event:

<body>
      ...
      <script src="/script.js"></script>
      <script>
           window.addEventListener('load', () => {
                injectLinkPrefetchIn4g('/product-details.html');
           });
      </script>
</body>

The landing page now prefetches product-details.html only on fast connections. To verify that:

  1. To preview the site, press View App. Then press Fullscreen fullscreen.
  2. Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
  3. Click the Network tab.
  4. In the Throttling drop-down list, select Online.
  5. Reload the app.

You should see product-details.html in the Network panel:

Network panel showing product-details.html prefetched.

To verify that the product page isn't prefetched on slow connections:

  1. In the Throttling drop-down list, select Slow 3G.
  2. Reload the app.

The Network panel should include only the resources for the landing page without product-details.html:

Network panel showing product-details.html not being prefetched.

The HTTP Link header can be used to prefetch the same type of resources as the link tag. Deciding when to use one or the other mostly depends on your preference, as the difference in performance is insignificant. In this case, you'll use it to prefetch the main CSS of the product page, to further improve its rendering.

Add an HTTP Link header for style-product.css in the server response for the landing page:

  1. Open the server.js file and look for the get() handler for the root url: /.
  2. Add the following line at the beginning of the handler:
app.get('/', function(request, response) {
    response.set('Link', '</style-product.css>; rel=prefetch');
    response.sendFile(__dirname + '/views/index.html');
});
  1. To preview the site, press View App. Then press Fullscreen fullscreen.
  2. Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
  3. Click the Network tab.
  4. Reload the app.

The style-product.css is now prefetched at the lowest priority after the landing page loads:

Network panel showing style-product.css prefetched.

To navigate to the product page, click Buy now. Take a look at the Network panel:

Network panel showing style-product.css retrieved from prefetch cache.

The style-product.css file is retrieved from the "prefetch cache" and it took only 12 ms to load.