← Back to Tutorials Chapter 14

Updates & Enhancements

Service Worker Update Lifecycle

When you deploy a new Service Worker, it goes through a specific lifecycle:

  1. Browser detects the new SW file (byte-different)
  2. New SW installs in the background
  3. New SW enters "waiting" state until all pages using the old SW are closed
  4. New SW activates and takes control
// Detect Service Worker updates
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(registration => {
    // Check for updates every minute
    setInterval(() => {
      registration.update();
    }, 60 * 1000);

    // Listen for new SW
    registration.addEventListener('updatefound', () => {
      const newWorker = registration.installing;
      newWorker.addEventListener('statechange', () => {
        if (newWorker.state === 'installed') {
          if (navigator.serviceWorker.controller) {
            // New version available!
            showUpdateNotification();
          }
        }
      });
    });
  });
}

Update Strategies

1. Immediate Update (Skip Waiting)

// sw.js
self.addEventListener('install', () => {
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  event.waitUntil(self.clients.claim());
});

// page.js - Force update when user clicks "Update"
function applyUpdate() {
  if (window.newWorker) {
    window.newWorker.postMessage({ type: 'SKIP_WAITING' });
  }
}

// sw.js
self.addEventListener('message', (event) => {
  if (event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

2. Gentle Update (Notify + Defer)

// Show a notification bar
function showUpdateNotification() {
  const banner = document.createElement('div');
  banner.className = 'update-banner';
  banner.innerHTML = `
    A new version is available.
    <button onclick="applyUpdate()">Update</button>
    <button onclick="dismissUpdate()">Later</button>
  `;
  document.body.prepend(banner);
}

// Listen for controller change (after update)
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (refreshing) return;
  refreshing = true;
  window.location.reload();
});

Versioning

// sw.js - Use cache versioning
const CACHE_VERSION = 'v2';
const STATIC_CACHE = `static-${CACHE_VERSION}`;
const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`;

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.map(key => {
          if (key !== STATIC_CACHE && key !== DYNAMIC_CACHE) {
            return caches.delete(key);
          }
        })
      );
    })
  );
});

Progressive Enhancement

Progressive enhancement means your PWA should work as a basic website even without JavaScript, then enhance when more capabilities are available.

// Check feature availability
const features = {
  serviceWorker: 'serviceWorker' in navigator,
  cache: 'caches' in window,
  indexedDB: 'indexedDB' in window,
  notifications: 'Notification' in window,
  pushManager: 'PushManager' in window,
  sync: 'SyncManager' in window,
  bluetooth: 'bluetooth' in navigator,
  usb: 'usb' in navigator,
  nfc: 'nfc' in navigator
};

// Progressively enhance based on available features
if (features.serviceWorker) {
  // Register SW for offline support
}
if (features.notifications) {
  // Enable push notifications
}
if (features.sync) {
  // Enable background sync
}
Exercise: Implement a versioned cache strategy for a PWA. When a new version is deployed, show a "New version available" toast notification. When the user clicks "Update", activate the new Service Worker and refresh the page.