Service workers: a guide to building offline web experiences

By Andreas Kleeman

Service workers are enabling brands to deliver powerful digital experiences offline, or on low-quality mobile networks. But what exactly is a service worker, what are the uses cases, and how can you start programming one for your website or web application? Let’s take a look.

What is a Service Worker (SW)?

A service worker (SW) is a new browser technology that’s enabling offline experiences on the web. Today they’re allowing progressive web apps (PWAs) to provide app-like experiences within the browser, and in the future we could see them used to enable features such as periodic sync and geofencing.

Service workers are JavaScript files that control the behaviour of your website, working together with a browser’s API. They act as an additional layer between the presentation of a website and the server from which all the data is sourced. The task of developing one for a site or web application is usually handled by a frontend developer.

The easiest way to think about service workers is as a piece of code that is installed by a site on a client machine, runs in the background, and subsequently enables requests sent to that site to be intercepted and manipulated.

Simon Jones

Where do service workers come from?

Service workers are recognised as a replacement for AppCache technology, an application caching mechanism that allows web-based applications to run offline, which ultimately failed because it failed to meet the needs of modern development environments. With this new browser technology, functionality that would normally require a native application is brought to the web.

Development began fairly recently – some time in early 2014 – and today they’re stable, far better at handling offline caching than AppCache, and supported by a broader range of web browsers (no longer just Chrome and Firefox).

They’re a very user-centred technology since all the data they handle is on the client’s side, and they’re made for Offline First strategy – a required part of a progressive web app.

I'm biased, but I think the service worker changes the scope of the web more than any feature since XHR.

Jake Archibald, Google Chrome

What is a service worker used for?

Service workers are used for everything from site performance enhancements, to progressive web apps.

The benefits for website users include:

  • Faster load times. SWs take a cache-first approach that reduces the strain of network requests and enables users to get to assets faster. They enhance the browsing experience by pre-fetching data – for example about the article or page a user is likely to visit next.
  • Offline browsing. The service browser acts as a proxy server between the app and the browser, with no network connection needed to deliver digital experiences. 

The business benefits of developing a service worker include:

  • Customised offline fallback experiences. The new browser technology allows brands to provide a customised offline ‘fallback’ experience, such as offline pages similar to 404 pages.
  • Native-like experiences in-browser. SWs are one of the building blocks for progressive web apps. They allows PWAs to work independent of connectivity and support background syncing for fast and fresh mobile experiences.
  • User engagement. SWs enable native functionality that can drive engagement, such as push notifications.
  • Load balancing. A SW can enable you to load from a desired server and prevent overloads.

And benefits for developers include:

  • Granular cache control. This allows developers to control how the app updates, and to control how Google Analytics can work while offline.
  • No blocking. Service workers are loaded on the page asynchronously, so they won't increase the time to DOM ready, or block any other page assets from loading

How to set up a SW

1. Set up registration

An SW needs to be initially registered inside your favorite JavaScript file.

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
             .register('./service-worker.js')
             .then(function() { console.log('Service Worker Registered'); });
}

This is described the so-called scope (irrespective of whether the SW controls your whole web app, or just a part of it, such as blog pages).

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
             .register('./blog/service-worker.js')
             .then(function() { console.log('SW Registered for Blog pages'); });
}

The call to register should be added as progressive enhancement, otherwise older browsers will throw up an error.

2. Start the download

Once the SW is registered the download will begin.

3. Install the service worker

After installing the service worker, files are added to the browser's cache storage:

var cacheName = 'myFilesToCache-v1';
var filesToCache = [
    './',
    './index.html',
    './scripts/app.js',
    './inline.css',
    './images/wind.png'
  ];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');

  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
        console.log('[ServiceWorker] Caching app shell');
        return cache.addAll(filesToCache);
    })
  );
});

4. Activate

This step is used to remove old caches when there are new. Old caches will get identified by the name of a newer cache.

var cacheName = 'myFilesToCache-v2';
// var filesToCache here
// install event here

self.addEventListener('activate', function(e) {
    console.log('[ServiceWorker] Activate');

    e.waitUntil(
        caches.keys().then(function(keyList) {
          return Promise.all(keyList.map(function(key) {
            if (key !== cacheName) {
              console.log('[ServiceWorker] Removing old cache', key);
              return caches.delete(key);
            }
          }));
        })
    );

    return self.clients.claim();
});

The below diagram, courtesy of Mozilla, nicely illustrates the steps to setting-up an SW:

diagram illustrating the steps to setting-up an SW

The lifecycle of an SW, according to Mozilla

Requirements and restrictions

1. Browsers

A table showing browser compatibility

A useful browser compatibility table, courtesy of Caniuse

All major browsers already support SW in their latest version, or will do in the near future. 

Mobile browsers such as Samsung Internet have been supporting SW for quite a long time, while Apple took a longer time to decide to support the technology in Safari. 

Head here to get the latest on SW development.

2. HTTPS

Because of security reasons, SW will just run on sites delivered over HTTPS. But it's also possible to run them on http://localhost for development purposes. This is considered to be a ‘secure origin’. Head to the Chromium project page for more detailed information on this.

3. Cache Limitation

The available storage capacity for the SW is limited by the browser's cache or quota API as the below table from Google, illustrates:

table showing available storage capacity for the SW

4. Scope

A service worker is only active for the user if they're in the right subset of the website's domain. A single SW can control many pages; each time a page within your scope is loaded, the SW is installed against that page and operates on it.  

5. User-centricity

A SW stays active until all tabs or windows with your web app or website are closed. This is to prevent data corruption and other negative experiences that could impact the browsing experience. When a user re-opens your web app a new SW can get active; before that, they have to wait.  

6. Waiting

A new cache is opened on SW’s install event when there is an changed version number or different cache name: 

var cacheName = 'weatherPWAs-v6'

The new SW has to wait when this is happening:

screenshot of an open SW debugging panel in the browser

Screenshot of an open SW debugging panel in the browser

Debugging

When a developer is developing a SW, they’ll need to check if it's working correctly. Bugs can emerge when things aren’t working quite right, so let’s explore how best debug.

Debugging in Chrome

In the following example you can see how to open the application panel in the Chrome browser dev tool to get information about the running SW and what is already cached.

screenshot showing debugging a SW in Chrome

Debugging a SW in Chrome

There are some useful things to know when developing a service worker to help prevent the emergence of bugs. It's very useful to frequently:

  • clear cache storage
  • test the service worker offline
  • update on reload

Note that it’s possible to clear the cache and update on reload (while reloading the website) with the following shortcut for Macs: cmd + shift + r 

The below screenshot illustrates some of the actions we’ve just discussed:

screenshot showing some of the actions just discussed

Debugging in Firefox

It's also possible to debug a SWin the Firefox browser. Currently this is not as native as it is in Chrome, but it’s worth Firefox fans checking out this Mozilla guide to debugging SWs.

Handy tools for building SWs

Workbox

The tools sw-toolbox and sw-precache may be obsolete, but that doesn’t mean they aren’t useful anymore. The team that created them have turned their attention towards building Workbox, a collection of libraries and tools that make it easy to build offline web apps. It’s a blend of sw-toolbox and sw-precache with more features and a modern codebase.

Workbox is a toolkit for automatically generating SW scripts, making it easy to store a site’s files locally on the user’s device. For anyone looking to make their site work offline or improve load performance on repeat visits, it’s a fantastic resource.

Google provides some useful examples on how to use Workbox with Webpack, Gulp, or NPM.

Using Workbox with Node

In your workbox-cli-config.js file:

module.exports = {
    "globDirectory": "./",
    "globPatterns": [
        "site/templates/css/style.min.css",
        "site/templates/js/build/production.min.js",
        "site/templates/fonts/**/*.{eot,svg,ttf,woff}"
    ],
    "swDest": "sw.js",
    "clientsClaim": true,
    "skipWaiting": true
};

And generate your SW script with: workbox generate:sw

Using Workbox with Gulp

gulp.task('generate-service-worker', () => {
    return workbox.generateSW({
        globDirectory: buildDir,
        globPatterns: ['**\/*.{png,css,js,svg,jpg,json}'],
        swDest: buildDir+`/sw.js`,
        clientsClaim: true,
        skipWaiting: true
    }).then(() => {
        console.info('Service worker generation completed.');
    }).catch((error) => {
        console.warn('Service worker generation failed: ' + error);
    });
});

In this example, skipWaiting is also used, which enables the developer to let a new SW get active, even if the user has not closed all tabs and windows as your website runs.

Of course, in opting for this little trick, the developer should really think about whether data can get corrupted in the process. But used to simply cache the likes of CSS assets, it should be relatively harmless.

Building an app that works offline

I hope you’ve enjoyed this introduction to service workers and now have a better understanding of the business benefits of this emerging browser technology.

From a developer perspective, SWs bring web app development closer to native, providing lots of control over the default browser caching system. The SW is a great starting point for transforming areas of a website into a single page application, or even a progressive web app. From a brand perspective, it’s the gateway to better digital experiences, even when there’s no internet connection.

Related reading