before your PWA go into the wild

Maxim Salnikov


Tame your Service Worker

How to build fast, offline-ready, user-friendly web apps

And enjoy doing this

Maxim Salnikov


  • Google Developer Expert in Angular

  • Mobile Oslo / Angular Oslo / PWA Oslo meetups organizer

  • Mobile Era conference organizer

Products from the future

UI Engineer at ForgeRock

Service worker

What kind of animal is it?

Progressive Web App

... web apps that use the latest web technologies.

... attempts to combine features offered by most modern browsers with the benefits of mobile experience

10 characteristics

  • Progressive

  • Discoverable

  • Linkable

  • App-like

  • Responsive

  • Connectivity-independent

  • Re-engageable

  • Installable

  • Fresh

  • Safe

Service Worker API

Know your toolset

  • Service Worker API

  • Cache API

  • IndexedDB

  • Fetch

  • Clients API

  • Broadcast Channel API

  • Push API

  • Notifications API

  • Local Storage

  • Session Storage

  • XMLHttpRequest

  • DOM

Service worker

  • Make some features of the web app function offline

  • Improve online performance by reducing network requests

Not only networking

  • Receiving push events and displaying notifications

  • Clients (tabs) messaging

  • Job scheduling

  • Responding to resource requests from other origins






Service worker










In Development

Behind the flag

Is service worker ready?

Progressive Web Apps

favoring or advocating progress, change, improvement, or reform

happening or developing gradually or in stages

Progressive enhancement

Go for the feature detection



if ('serviceWorker' in navigator) {

    // Registering service worker


Background syncronization

if ('SyncManager' in window) {

    // Implement offline-ready network features


Push subscription

if (!('PushManager' in window)) {

    // Hide UI for Web Push subscription


Actions in notifications

if ('actions' in Notification.prototype) {

  // Consider using action buttons



Proper time to register

The later the better


if ('serviceWorker' in navigator) {


if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
  .then(() => {

    // Service worker registration



// Registered
navigator.serviceWorker.register('/news/sw.js', {scope: '/news/2017/'});


// Registered if 'Service-Worker-Allowed' header is set to ‘/’
// NOT registered
navigator.serviceWorker.register('/news/sw.js', {scope: '/catalog/'});
// NOT registered
navigator.serviceWorker.register('/news/sw.js', {scope: '/'});

In case of emergency

Implement a Kill Switch


No animals were harmed

  • Deploy fixed or no-op service worker

  • Make sure that browser will not serve service worker file from HTTP cache

  • ... or unregister service worker

Rescue plan


self.addEventListener('install', () => {
self.addEventListener('activate', () => {
  self.clients.matchAll({type: 'window'}).then(tabs => {
    tabs.forEach(tab => {

UX breaking

Update & bypass HTTP cache

Cache-Control: max-age=0

Server side

Service worker

Spec will be updated

  • Byte-different - add versioning

  • Byte check doesn't work for scripts imported with importScripts()


    .then((registrations) => {
        for(let registration of registrations) {



Trust your assets and be intolerant to the outdated ones


const appShellFilesToCache = [


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

  event.waitUntil( => {
      return cache.addAll(appShellFilesToCache)

  • HTTP errors

  • Service worker execution time

  • Storage errors

Chrome <6% of free space
Firefox <10% of free space
Safari <50MB
IE10 <250MB
Edge Dependent on volume size

Storage is not unlimited

  • Quota Management API

  • Storage Quota Estimate API


Caching from other origins

Get ready for opaque


3 options

  • Add CORS headers on remote side

  • Handle opaque responses

  • Use experimental foreign fetch

Opaque responses limitations

  • Valid for limited set of elements: <script>, <link rel="stylesheet">, <img>, <video>, <audio>, <object>, <embed>, <iframe>

  • The status property of an opaque response is always set to 0, regardless of whether the original request succeeded or failed

  • The Cache API's add()/addAll() methods will both reject if the responses resulting from any of the requests have a status code that isn't in the 2XX range

const appShellFilesToCache = [


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

  event.waitUntil( => {
      return cache.addAll(appShellFilesToCache)


Solution for no-cors

const noCorsRequest =
    new Request('', {
        mode: 'no-cors'

    .then(response => cache.put(noCorsRequest, response));


To follow or not to follow?


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

  event.waitUntil( => {
      return cache.addAll(appShellFilesToCache)

const appShellFilesToCache = [


app.get('/assets/redirect/redirectfrom.html', (req, res) => {
    res.redirect(301, '/assets/redirect/redirectto.html')


If one of the following conditions is true, then return a network error:

  • ...

  • request’s redirect mode is not "follow" and response’s url list has more than one item.

Redirect mode of navigation request is “manual”


  • To avoid the risk of open redirectors introduce a new security restriction which disallows service workers to respond to requests with a redirect mode different from "follow".

  • Add .redirected attribute to Response class of Fetch API. Web developers can check it to avoid untrustworthy responses.

  • Do not pre-cache redirected URLs

  • "Clean" the response before responding

Solutions for 3xx

Do not pre-cache 3xx at all




// If "cleanRedirects" and this is a redirected response,
// then get a "clean" copy to add to the cache.

const newResponse = cleanRedirects && response.redirected ?
    await cleanResponseCopy({response}) :


"Clean" the response

Proper tools

Be there or be square


Tools helps with

  • Implementing complex algorithms

  • Following specifications updates

  • Handling edge cases

  • Adopting best practices

  • Focusing on YOUR task


  • sw-precache / sw-toolbox

  • Workbox

  • offline-plugin for Webpack

  • create-react-app

  • preact-cli

  • polymer-cli

  • vue-cli

  • angular-cli


App shell

Runtime caching

Offline GA

Replay failed requests

Broadcast updates

Build integrations

Possibility to extend your own service worker instead of using generated one


Service Worker Mock

const env = {
  // Environment polyfills
  skipWaiting: Function,
  caches: CacheStorage,
  clients: Clients,
  registration: ServiceWorkerRegistration,
  addEventListener: Function,
  Request: constructor Function,
  Response: constructor Function,
  URL: constructor Function

Push notifications

Don't convert them to "new pop-ups"


  • Initiate only after explicit user action

  • Keep unsubscribe functionality visible 



  • Consider alternative ways first - displaying notification is the last resort

  • Not for broadcasting, but for individual approach


Online check in is available





21.09 13:45


Click here to check in now


  • Do not Repeat Yourself

  • Provide actual information instead of notifying about it

  • Give a call to action

If Web Push doesn't work

  • Subscription is valid 

  • Event listener push exists

  • There is self.registration.showNotification()

Upcoming features

Not so mature yet


Navigation preload

SW Boot

Navigation request

SW Boot

Navigation request

self.addEventListener('activate', e => {
self.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);

Periodic sync

  • Restricted by time interval, battery state and network state

  • Would require user permission

  • Don't require any server configuration

  • Allow the user agent to optimize when they fire

navigator.serviceWorker.ready.then((registration) => {
    tag: 'get-latest-news',         // default: ''
    minPeriod: 12 * 60 * 60 * 1000, // default: 0
    powerState: 'avoid-draining',   // default: 'auto'
    networkState: 'avoid-cellular'  // default: 'online'
  }).then((periodicSyncReg) => {

    // Successfully registered



self.addEventListener('periodicsync', function(event) {
  if (event.registration.tag == 'get-latest-news') {
  else {
    // Unknown sync, may be old, best to unregister


Foreign fetch

  • Intercepting cross-origin requests from any client

  • Common resource cache

  • Network-independent version of service





Link: </sw-fontservice.js>; rel="serviceworker"; scope="/"
self.addEventListener('install', event => {
    scopes: ['/fonts'], // or self.registration.scope
    origins: ['*'] // or ['']

self.addEventListener('foreignfetch', ...)



  • 700+ developers

  • Major browsers/frameworks/libs devs


Tusen takk!


Maxim Salnikov


Tame your Service Worker before your Progressive Web App go into the wild

By Maxim Salnikov

Tame your Service Worker before your Progressive Web App go into the wild

The collection of modern web browsers APIs and set of best practices on creating the applications turned into a new software creation methodology called Progressive Web Apps (PWA). The Service Worker API is a key API of the whole concept. Let me unleash its power for you! But with great power comes great responsibility - trivially, but true: I'll show the examples of how easy the "Progressive" part of the PWA term could become "Regressive", how to fix this, and how to test our Service Worker before deploying your app. First, we'll go through the well-known PWA functionality (App Shell, Offline Cache, Push) with focusing on the pitfalls we could easily get into, and how to avoid these. Next, I'll expand your horizons with the new PWA features, like Foreign fetch, Periodic sync, Navigation Preloads. And again - "handle with care". I'll share the points we have to pay attention to, as well as best practices. As a practical result, you will get a full overview of basic and advanced Service Worker features, as well as knowledge on how to solve a real life project issues in the best possible way. BONUS: I'll share the latest additions to Service Worker and satellite APIs, so you will be ready to build the applications for the future!

  • 6,702