![Demián Renzulli](https://web.dev/images/authors/demianrenzulli.jpg) Demián Renzulli [X](https://twitter.com/drenzulli) [GitHub](https://github.com/demianrenzulli)

<br />

> [!NOTE]
> **Note:** This codelab uses Chrome DevTools. [Download Chrome](https://www.google.com/chrome) if you don't already have it.

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. Press \`Control+Shift+J\` (or \`Command+Option+J\` on Mac) to open DevTools.
2. Click the **Network** tab.

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

4. 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](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-loa-4b42143b7dc64.png)

## Prefetch the product page with `<link rel="prefetch">`

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](https://developer.mozilla.org/docs/Web/HTML/Element/link#Attributes).

To verify that prefetching is working:

1. Press \`Control+Shift+J\` (or \`Command+Option+J\` on Mac) to open DevTools.
2. Click the **Network** tab.

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

4. Clear the Disable cache checkbox.

5. 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.](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-pro-faf80ce6f71b9.png)

The page is kept in the [HTTP cache](https://developer.mozilla.org/docs/Web/HTTP/Caching) for five minutes, after which the normal [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/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.](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-pro-f3cc86ebec4f9.png)

## 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](https://developer.mozilla.org/docs/Web/API/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](https://developer.mozilla.org/docs/Web/API/NetworkInformation/effectiveType) property of the [Network Information API](https://developer.mozilla.org/docs/Web/API/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. Press \`Control+Shift+J\` (or \`Command+Option+J\` on Mac) to open DevTools.
2. Click the **Network** tab.
3. In the **Throttling** drop-down list, select **Online**.
4. Reload the app.

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

![Network panel showing product-details.html prefetched.](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-pro-6dc654d8b35cd.png)

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.](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-pro-b7e8defa3846.png)

## Prefetch the stylesheet for the product page with the HTTP `Link` header

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. Press \`Control+Shift+J\` (or \`Command+Option+J\` on Mac) to open DevTools.
2. Click the **Network** tab.
3. 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.](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-sty-7a1ff62d407bd.png)

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.](https://web.dev/static/articles/codelab-two-ways-to-prefetch/image/network-panel-showing-sty-82440f67f5952.png)

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

> [!NOTE]
> **Note:** When using HTTP `Link` header, you can decide whether to prefetch depending on the network conditions based on the information available in [client hints](https://web.dev/performance-optimizing-content-efficiency-client-hints).