Interested in learning JavaScript? Get my ebook at

Service workers are at the core of Progressive Web Apps. They allow caching of resources and push notifications, which are two of the main distinguishing features that have set native apps apart up to this point.

A service worker is a programmable proxy between your web page and the network which provides the ability to intercept and cache network requests. This effectively lets you create an offline-first experience for your app.

Service workers are a special kind of web worker: a JavaScript file associated with a web page which runs on a worker context, separate from the main thread. This gives the benefit of being non-blocking — so computations can be done without sacrificing the UI responsiveness.

Since it’s on a separate thread, it has no DOM access. Nor does it have access to the Local Storage APIs and the XHR API. It can only communicate back to the main thread using the Channel Messaging API.

Service Workers cooperate with other recent Web APIs:

  • Promises
  • Fetch API
  • Cache API

And they are only available on HTTPS protocol pages (except for local requests, which do not need a secure connection. This makes testing easier.).

Background Processing

Service workers run independently of the application they are associated with, and they can receive messages when they are not active.

For example they can work:

  • when your mobile application is in the background, not active
  • when your mobile application is closed and even not running in the background

The main scenarios where service workers are very useful are:

  • They can be used as a caching layer to handle network requests, and cache content to be used when offline
  • They can allow push notifications

A service worker only runs when needed, and it’s stopped when not used.

Offline Support

Traditionally, the offline experience for web apps has been very poor. Without a network, often mobile web apps simply won’t work. Native mobile apps, on the other hand, have the ability to offer either a working version or some kind of nice message.

This is not a nice message, but this is what a web pages look like in Chrome without a network connection:

Possibly the only nice thing about this is that you get to play a free game by clicking the dinosaur — but it gets boring pretty quickly.

In the recent past, the HTML5 AppCache already promised to allow web apps to cache resources and work offline. But its lack of flexibility and confusing behavior made it clear that it wasn’t good enough for the job (and it’s been discontinued).

Service workers are the new standard for offline caching.

Which kind of caching is possible?

Precache assets during installation



Caching network requests

Using the Fetch API, we can edit the response coming from the server, determining if the server is not reachable and providing a response from the cache instead.

A Service Worker Lifecycle

A service worker goes through three steps to become fully functional:

  • Registration
  • Installation
  • Activation



Example code to register a service worker placed in worker.js:

if ('serviceWorker' in navigator) {   window.addEventListener('load', () => {       navigator.serviceWorker.register('/worker.js')     .then((registration) => {       console.log('Service Worker registration completed with scope: ', registration.scope)     }, (err) => {       console.log('Service Worker registration failed', err)    })  })} else {   console.log('Service Workers not supported') }

Even if this code is called multiple times, the browser will only perform the registration if the service worker is new and not registered previously, or if it has been updated.





navigator.serviceWorker.register('/worker.js', {   scope: '/notifications/' })


{ scope: '/notifications' }

it would have worked.




self.addEventListener('install', (event) => {   //... });

This is a great time to prepare the service worker to be used by initializing a cache. Then cache the App Shell and static assets using the Cache API.


Once the service worker has been successfully registered and installed, the third step is activation.


It cannot interact with pages already loaded, so the service worker is only useful the second time the user interacts with the app or reloads one of the pages already open.

self.addEventListener('activate', (event) => {   //... });

A good use case for this event is to cleanup old caches and things associated with the old version that are unused in the new version of the service worker.

Updating a Service Worker

To update a service worker, you just need to change one byte in it. When the register code is run, it will be updated.

Once a service worker is updated, it won’t become available until all pages that were loaded with the old service worker attached are closed.

This ensures that nothing will break on the apps/pages that are already working.

Refreshing the page is not enough, as the old worker is still running and it hasn’t been removed.

Fetch Events



For example, the snippet below uses the Cache API to check if the requested URL was already stored in the cached responses. If that’s the case, it returns the cached response. Otherwise, it executes the fetch request and returns it.

self.addEventListener('fetch', (event) => {  event.respondWith(     caches.match(event.request)       .then((response) => {         if (response) {           //entry found in cache           return response         }         return fetch(event.request)       }     )   ) })

Background Sync


This is key to ensure that a user can use the app offline, take actions on it, and queue server-side updates for when there is a connection open (instead of showing an endless spinning wheel trying to get a signal).

navigator.serviceWorker.ready.then((swRegistration) => {   return swRegistration.sync.register('event1') });

This code listens for the event in the service worker:

self.addEventListener('sync', (event) => {   if (event.tag == 'event1') {     event.waitUntil(doSomething())   } })

doSomething() returns a promise. If it fails, another sync event will be scheduled to retry automatically until it succeeds.


Push Events

Service workers enable web apps to provide native Push Notifications to users.

Push and Notifications are actually two different concepts and technologies that are combined to provide what we know as Push Notifications. Push provides the mechanism that allows a server to send information to a service worker, and Notifications are the way service workers can show information to the user.

Since service workers run even when the app is not running, they can listen for push events coming. They then either provide user notifications, or update the state of the app.


Here is an example of how the web worker can listen for incoming push events:

self.addEventListener('push', (event) => {   console.log('Received a push event', event) 
  const options = {     title: 'I got a message for you!',     body: 'Here is the body of the message',     icon: '/img/icon-192x192.png',     tag: 'tag-for-this-notification',   } 
  event.waitUntil(     self.registration.showNotification(title, options)   ) })

A note about console logs:

If you have any console log statement (console.log and friends) in the service worker, make sure you turn on the Preserve log feature provided by the Chrome Devtools (or equivalent).


Interested in learning JavaScript? Get my ebook at