What Are Service Workers and How They Help Improve Performance

Service workers allow developers to manage resource caching more efficiently so that users don't experience interruptions even if they disconnect from the internet. This guide will explain how a service worker can propel your web app's performance to new heights.

What are service workers?

A service worker is a specific type of JavaScript worker, which is a script that runs in the background of the user's browser. Service workers are like proxy servers stationed between your app, the user's browser and the network. Among other things, service workers allow apps to continue functioning offline in case the user loses internet connection.

Service workers give developers greater control over how their apps process navigation requests. Delays in navigation requests leave users staring at a blank screen, which you want to avoid as much as possible.

How service workers improve performance

We've all been there: You're browsing a website, you click on a link and you're met with some variation of the message "Cannot connect to the internet." You hit the back button only to see the same message. You cannot do anything until a connection is reestablished except play dino runner if you're using Chrome. This scenario is preventable thanks to service workers.

One of the biggest benefits of service workers is their ability to support offline experiences. In the past, the AppCache API could be used to provide limited offline support, but service workers have now made AppCache obsolete.

Loading locally cached data always takes less time than retrieving data from the web. If you can cache all of your app's resources, then you can substantially improve load times for your returning visitors. While first impressions are important, offering a smoother experience to returning users increases the likelihood that they'll keep coming back.

How do service workers work?

Service worker scripts are independent of your website or app. They have their own life cycle, which is illustrated in the following graphic:

Once registered in a page's JavaScript, the service worker will be installed by the browser. At that point, a properly configured service worker would start caching static assets right away; if any errors prevent files from getting cached, then installation will fail, and you must try again.

If installation is a success, the service worker will activate and gain control over all pages under its scope. The page that registered the service worker must be reloaded for the worker to take effect. Now that the service worker is activated, it can alternate between two states: either it will handle fetch and message events in response to network requests, or it will terminate to save memory.

Service worker prerequisites

While service workers are wonderful for many reasons, they could theoretically provide avenues for hackers to infiltrate your web app. In order to protect against man-in-the-middle attacks, service workers can only be registered to pages that are served over HTTPS. Although you can use service workers through localhost during development, you must have HTTPS set up for your users to see any benefits. If you haven't migrated to HTTPS yet, check out our complete guide on how to do just that.

It's also worth noting that most major web browsers now support service workers with the exception of IE.

How to register a service worker

Service workers rely heavily on JavaScript promises, so it's helpful to have an understanding of how promises work before you get started.

To get started, you'll first need to register the service worker in your page. You can do this in a top level JavaScript file, for example app.js . This lets the browser know where your service worker's JavaScript file ( sw.js ) lives:

if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js').then(function(registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }, function(err) { console.log('ServiceWorker registration failed: ', err); }); }); }

The above example first checks to see if the service worker exists in the browser's navigator. If so, then sw.js is registered. You must reload the page to complete the process. If you're using Chrome DevTools, look under the service workers tab in the application panel to see if registration was successful.

How to install a service worker

Before your service worker can do anything, you must define an install event listener and choose which files you want cached. Open your sw.js file and add this code:

var cacheName = 'site-cache-v1'; var assetsToCache = ['asset.js']; self.addEventListener('install', function(event) { event.waitUntil( caches.open(cacheName) .then(function(cache) { console.log('Opened cache'); return cache.addAll(assetsToCache); }) ); });

In this example, the install event listener caches the specified assets in assetsToCache . You can add more assets as an array to the assetsToCache variable.

How to fetch events

Once installed successfully, the service worker will activate and the next step is to start returning some of your cached responses. For that, we can use the following snippet:

self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; } return fetch(event.request); } ) ); });

In the above snippet, a fetch event is defined and we pass the promise caches.match . Therefore, the method attempts to find any of the cached results created by your service worker. If it is unable to do so, a network request is made in an attempt to return the desired data.

This guide only covers the basics for getting started with service worker implementation. In order to continue delivering offline support, you must periodically update your service workers. Chrome DevTools has more information about updating service workers.

Service worker caching tips

Traditional caching practices require the browser to communicate with the network to refresh subresource URLs. Service workers provide a place to cache those subresources so that navigation requests can be processed regardless of network availability. How you should implement service workers depends on the architecture of your web app. Here are some tips to guide you:

1. Service workers and streamed responses

If most of your HTML documents feature static headers and footers, it's best to handle navigations with a streamed response composed of separately cached subresources. Streams allow your app to respond to navigation requests with cached chunks of HTML.

In some situations, such as when your HTML is dependent on data from your CMS, communication with the network is unavoidable. Nonetheless, implementing service workers and streamed responses still enhances the user experience since users will see the initial elements of a page right away while the rest of your HTML loads from the network.

2. Caching static HTML with service workers

Circumventing the network is simple if your web app is made up of static HTML documents.

You just have to implement a service worker that returns cached HTML in response to navigation requests. Of course, you also must include some non-blocking logic to keep the HTML up to date as you make changes to your site or app. This can be accomplished by establishing a stale-while-revalidate policy for navigation requests using a service worker fetch handler:

self.addEventListener('fetch', event = { if (event.request.mode === 'navigate') { event.respondWith(async function() { const normalizedUrl = new URL(event.request.url); normalizedUrl.search = ''; const fetchResponseP = fetch(normalizedUrl); const fetchResponseCloneP = fetchResponseP.then(r => r.clone()); event.waitUntil(async function() { const cache = await caches.open('my-cache-name'); await cache.put(normalizedUrl, await fetchResponseCloneP); }()); return (await caches.match(normalizedUrl)) || fetchResponseP; }()); } });

Alternatively, you could use Workbox to generate a service worker capable of caching your static resources and serving them cache-first while also keeping them up to date.

3. Service workers and single page applications

Integrating a service worker into your application shell architecture allows your single page apps to avoid the network when handling navigation requests. Once you've installed and activated some handlers to cache and update app-shell.html , add this code:

self.addEventListener('fetch', event => { if (event.request.mode === 'navigate') { event.respondWith(caches.match('app-shell.html')); } });

All navigation requests should now return a cached copy of an HTML document containing the essential elements of your app. The code above also includes client side routing logic to render content based on the underlying URL.

Measuring how service workers impact performance

Of course, appropriately quantifying "web performance" requires a data-driven approach. A Google engineer published a case study examining how the creators of the Google I/O web app measured the performance impact of service workers using Google Analytics.

The developers began by listing the questions they hoped to answer with the collected data. First, they wanted to know if service worker caching is more efficient than the default HTTP caching mechanisms. Users already expect pages to load faster on return visits, so it's important to know if service workers are actually improving speed.

Next, they had to decide which metrics best represent how fast their website "feels" like it's loading. "Page load time" doesn't tell us when an app becomes interactive. Metrics like time to interactive and the first meaningful paint are better indicators of when users perceive your website as loaded. Once you've identified the right questions, and you know which metrics will provide the answers, then you're ready to start evaluating your optimization efforts.

Summary

Service workers can do more than just improve web performance. For example, they can be used to implement push notifications, which is a great way to keep users engaged with mobile apps. Future applications for service workers might include things like geofencing. In other words, it's a good idea to start experimenting with service workers because they're not going away anytime soon.