Enable Browser Hint Pre-Load Support in Your Service Worker to Improve Page Speed

Service Worker Navigation Preload
Service Worker Navigation Preload

There are many subtle tricks to improve a site's page speed. One of the more advanced techniques is to use browser hints, which you can read about here.

One of these browser hints is 'preload', which causes the browser to start loading resources earlier than when they would normally be due to their position in the markup.

For example you can load scripts, images and other resources early. This does not mean the content will be processed, it just initiates the network request.

A problem might arise when using the preload hint and a service worker. The service worker must 'boot' before it handles network requests.

The Service Worker Life Cycle

Service workers do not run 24/7. If they did they could and would drain device batteries and consume extra CPU cycles. This would slow down the computer or mobile device as well as force users to constantly charge the device.

Instead service workers go dormant, much like an AWS Lambda or Azure function does, when not being used.

Even if the user has the site up and running, if the service worker is not used over a period of time the browser silently turns it off until needed.

This means the effect of the preload could be lost while waiting on the service worker to initiate. To mitigate the problem some browsers have begun allowing you to enable preload support in service workers.

The Service Worker and Preload Problem

Assuming the site's service worker is not active, a preload network request could be blocked, waiting for the service worker to boot.

Normal Service Worker Boot Sequence
Normal Service Worker Boot Sequence

Despite improving the overall page speed profile of a website a service worker must be running to improve network latencies through caching.

Service workers must boot, just like your operating system or native app. This boot time is typically fast, 50ms or less on higher powered devices. But could take as long as 250ms on cheap mobile handsets.

Now, if the service worker is not active the preload request will proceed in parallel with the service worker process.

This also means the service worker needs to account for the possibility of the network request being a preload. This means your fetch event handler logic must handle this scenario.

As of Chrome version 59 (a long time ago), the service worker navigationPreload features have been supported by default.

This enables a GET request to proceed in parallel with the service worker boot.

Service Worker Preload Boot
Service Worker Preload Boot

The Service Worker Navigation PreLoad Error Message

If you use the preload browser hint without using the service worker navigationPreload plumbing in Chromium based browsers you most likely will see the following error message in the browser console:

The service worker navigation preload request was cancelled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle.

This triggers when a preload request is attempted before the service worker has booted.

The browser is telling us the preload request was effectively blocked because the service worker did not account for the network request before the boot completed.

It also means the service worker fetch event handler is not using the available preloadResponse.

As I will demonstrate if navigation preload is supported you can add special APIs in your fetch handler to account for parallel requests.

Checking for Service Worker Navigation Preload Support

Because not all browsers support the service worker navigation preload functionality you will need to use feature detection to determine if you can use the API:

There are two methods you need to hook into to enable navigation preLoad support, activate and fetch.

In the service worker activate event you need to do feature detection for the navigationPreload object.

The navigationPreload object is a member of the service worker Registration object.

self.addEventListener( "activate", event => {

    event.waitUntil( async function () {
        // Feature-detect
        if ( self.registration.navigationPreload ) {
            // Enable navigation preloads!
            console.log( "Enable navigation preloads!" );

            await self.registration.navigationPreload.enable();
        }

            return;

        } )

    }() );

} );

If the service worker registration object contains the navigationPreload member you can safely call its enable method.

This turns the functionality on for the fetch event handler. You can also call the disable method to turn the preFetch support off.

Both the enable and disable methods return a promise. Remember everything in a service worker must be asynchronous.

Instead of working with a stack of promise thens, you can use the await attribute to effectively turn it into a synchronous method. Neither method resolve any value.

Also note this needs to be done within the responseWith method. If you are not familiar with this method it effectively holds the event handler door open until all the functionality completes.

Supporting preFetch Requests in the Service Worker fetch Event Handler

Now that preload responses are enabled in the service worker the fetch event handler should also account for them.

To do this you will need to add support for catching a preloadResponse to your handling logic.

addEventListener("fetch", event => {
  event.respondWith(async function() {
    // Respond from the cache if we can
    const cachedResponse = await caches.match(event.request);
    if (cachedResponse) return cachedResponse;

    // Else, use the preloaded response, if it's there
    const response = await event.preloadResponse;
    if (response) return response;

    // Else try the network.
    return fetch(event.request);
  }());
});

Just like finding a match within a service worker cache the preloadResponse is asynchronous, returning a response object.

Above is the standard service worker cache first, then network pattern. Now the pattern adds support for the preloadResponse.

Of course if a cached response is available you should return it first, at least most of the time. Before you do a network request check to see if the preload request was triggered with a valid response.

Why not just use a normal fetch?

Well, its complicated and requires digging into the browser plumbing. This is not really worthwhile, so lets look at the higher level, which is easier to digest.

When the browser initiates the preload request is does it in the background. Which means the request has already been initiated.

If you make a new fetch request it means the resource will be requested across the network twice. This is what we need to avoid.

You can think of the preloadResponse as sort of a special background queued response. It may be there, and it may not. Either way the response will be resolved (asynchronously) as soon as the request completes.

If the request was not initiated by a preload hint then the method immediately resolves with an undefined response object. This means you will just naturally fall back to the network request fetch.

Summary

The service worker navigation preload API was brought to my attention by seeing the error or warning message in the browser console. Well that and one of the Microsoft Edge engineers pointed it out to me one day while we were reviewing some things.

While not using this API wont break anything pre se, it should be something you implement in your service workers.

By doing so your pages will be able to take advantage of the browser hint preload functionality without it being negated by the service worker boot sequence.

Again this is one of those small features you need to be aware of when you are creating your site's service worker.

It is just one example of the many scenarios and features you need to manage to have a good service worker and be able to take full advantage of the performance benefits they offer.

Share This Article With Your Friends!

Googles Ads Facebook Pixel Bing Pixel LinkedIn Pixel