Skip to content

Service Workers

This lesson explains Service Workers with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.

HTML5 Service Workers Overview

Service Workers are background scripts that run separately from the main web page. They allow web applications to intercept network requests, cache files, provide offline support, send push notifications, and create Progressive Web App experiences.

Service Workers are commonly used for offline apps, app shell caching, background sync, push notifications, asset caching, API response caching, faster page loading, and reliable mobile-first web applications.

Feature Description
API Name Service Workers API
Main Object navigator.serviceWorker
Main File service-worker.js
Main Events install, activate, fetch, push, sync
Security Requires HTTPS in production.
Best Use Offline support, caching, PWAs, background features.

Register a Service Worker

// main.js

if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/service-worker.js")
      .then((registration) => {
        console.log(
          "Service Worker registered:",
          registration.scope
        );
      })
      .catch((error) => {
        console.error(
          "Service Worker registration failed:",
          error
        );
      });
  });
}

The service worker is registered from the main JavaScript file. After registration, the browser downloads, installs, and activates the worker.

Service Worker Lifecycle

Lifecycle Step Description Common Use
Register Browser registers the service worker file. Start service worker setup.
Install Service worker installs for the first time or after an update. Cache app shell files.
Activate New service worker becomes active. Delete old caches.
Fetch Service worker intercepts network requests. Serve cache or network response.
Update Browser detects changed service worker file. Install new version.

Install Event and Cache App Shell

// service-worker.js

const CACHE_NAME =
  "app-cache-v1";

const APP_SHELL =
  [
    "/",
    "/offline",
    "/styles/main.css",
    "/scripts/app.js",
    "/icons/icon-192.png"
  ];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => {
        return cache.addAll(APP_SHELL);
      })
  );
});

The install event is commonly used to cache important files required for the application shell.

Activate Event and Delete Old Caches

// service-worker.js

const CURRENT_CACHE =
  "app-cache-v2";

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys()
      .then((cacheNames) => {
        return Promise.all(
          cacheNames.map((cacheName) => {
            if (cacheName !== CURRENT_CACHE) {
              return caches.delete(cacheName);
            }
          })
        );
      })
  );
});

The activate event is useful for removing old cache versions and preparing the new service worker.

Fetch Event Example

// service-worker.js

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((cachedResponse) => {
        if (cachedResponse) {
          return cachedResponse;
        }

        return fetch(event.request);
      })
  );
});

The fetch event allows the service worker to intercept requests and decide whether to return cached data or make a network request.

Cache-First Strategy

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((cachedResponse) => {
        return cachedResponse || fetch(event.request);
      })
  );
});

Cache-first is best for static files such as CSS, JavaScript, images, icons, fonts, and app shell files.

Network-First Strategy

self.addEventListener("fetch", (event) => {
  event.respondWith(
    fetch(event.request)
      .then((networkResponse) => {
        const responseClone =
          networkResponse.clone();

        caches.open("dynamic-cache-v1")
          .then((cache) => {
            cache.put(
              event.request,
              responseClone
            );
          });

        return networkResponse;
      })
      .catch(() => {
        return caches.match(event.request);
      })
  );
});

Network-first is useful for dynamic content that should be fresh when online but still available from cache when offline.

Offline Fallback Page

self.addEventListener("fetch", (event) => {
  if (event.request.mode === "navigate") {
    event.respondWith(
      fetch(event.request)
        .catch(() => {
          return caches.match("/offline");
        })
    );
  }
});

Offline fallback pages provide a better experience than showing the browser's default network error page.

Cache API Methods Used with Service Workers

Method Description Example Syntax
caches.open() Opens or creates a named cache. caches.open("app-v1")
cache.addAll() Adds multiple files to cache. cache.addAll(files)
cache.put() Stores a request and response pair. cache.put(request, response)
caches.match() Finds a matching cached response. caches.match(request)
caches.keys() Returns all cache names. caches.keys()
caches.delete() Deletes a cache. caches.delete("old-cache")

Activate New Service Worker Immediately

// service-worker.js

self.addEventListener("install", () => {
  self.skipWaiting();
});

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

skipWaiting() activates the new worker faster, and clients.claim() allows it to control open pages immediately. Use this carefully when cache changes may affect active users.

Send Messages Between Page and Service Worker

// main.js

navigator.serviceWorker.ready
  .then((registration) => {
    registration.active.postMessage(
      {
        type: "CLEAR_CACHE"
      }
    );
  });
// service-worker.js

self.addEventListener("message", (event) => {
  if (event.data.type === "CLEAR_CACHE") {
    caches.keys()
      .then((names) => {
        names.forEach((name) => {
          caches.delete(name);
        });
      });
  }
});

Messaging allows the page and service worker to coordinate cache updates, logout cleanup, or background tasks.

Background Sync Example

// main.js

async function registerSync() {
  const registration =
    await navigator.serviceWorker.ready;

  if ("sync" in registration) {
    await registration.sync.register(
      "sync-orders"
    );
  }
}
// service-worker.js

self.addEventListener("sync", (event) => {
  if (event.tag === "sync-orders") {
    event.waitUntil(
      syncPendingOrders()
    );
  }
});

Background Sync can retry failed actions when the network returns. Always provide a manual retry fallback because browser support varies.

Push Notification Example

// service-worker.js

self.addEventListener("push", (event) => {
  const data =
    event.data
      ? event.data.json()
      : {
          title: "New Update",
          body: "You have a new notification."
        };

  event.waitUntil(
    self.registration.showNotification(
      data.title,
      {
        body: data.body,
        icon: "/icons/icon-192.png"
      }
    )
  );
});

Service Workers can display notifications even when the page is not open, making them important for Progressive Web Apps.

Handle Notification Click

// service-worker.js

self.addEventListener("notificationclick", (event) => {
  event.notification.close();

  event.waitUntil(
    clients.openWindow("/notifications")
  );
});

The notificationclick event helps users return to the correct page when they click a notification.

Common Service Worker Events

Event Purpose Common Use
install Runs when service worker installs. Pre-cache app shell.
activate Runs when service worker activates. Clean old caches.
fetch Intercepts network requests. Offline and caching strategies.
message Receives messages from page. Cache cleanup, update commands.
sync Runs background sync tasks. Retry failed form submissions.
push Receives push messages. Show push notifications.
notificationclick Runs when user clicks notification. Open related app page.

Service Worker vs Web Worker

Feature Service Worker Web Worker
Main Purpose Network proxy, offline support, caching, push. Background JavaScript processing.
Controls Network Yes. No.
Can Access DOM No. No.
Lifecycle Event-driven and can stop when idle. Runs while page keeps it alive.
Common Use PWA, offline, cache, push notifications. Heavy calculations, parsing, image processing.

Feature Detection

if ("serviceWorker" in navigator) {
  console.log("Service Workers are supported.");
} else {
  console.log("Service Workers are not supported.");
}

Always check support before registering a service worker. Service Workers require HTTPS in production, but they work on localhost during development.

Common Service Worker Use Cases

  • Offline-first Progressive Web Apps.
  • App shell caching.
  • Offline fallback pages.
  • Static asset caching.
  • API response caching.
  • Background Sync for failed requests.
  • Push notifications.
  • Faster repeat page loads.

Service Worker Best Practices

  • Use HTTPS in production.
  • Keep the service worker file small and focused.
  • Version caches clearly.
  • Delete old caches during activation.
  • Use the correct caching strategy for each request type.
  • Do not cache sensitive account or payment pages by default.
  • Provide offline fallback pages for navigation requests.
  • Test updates, offline mode, and cache clearing in browser DevTools.

Security and Privacy Considerations

  • Service Workers can intercept requests, so only register trusted scripts.
  • Do not cache private data unless there is a clear security plan.
  • Clear user-specific caches during logout.
  • Validate cached API responses before rendering.
  • Use HTTPS and secure cookies for authenticated sessions.
  • Be careful when caching pages that contain personal information.
  • Do not expose secrets in service worker code.
  • Keep push notification content privacy-friendly.

Common Service Worker Mistakes

  • Registering a service worker without feature detection.
  • Forgetting HTTPS requirements in production.
  • Caching everything without a strategy.
  • Not deleting old caches after updates.
  • Breaking refresh or deep-link behavior offline.
  • Serving stale files after deployment.
  • Not handling failed network requests.
  • Caching sensitive user data accidentally.

Key Takeaways

  • Service Workers run in the background separately from web pages.
  • They enable offline support, caching, push notifications, and background sync.
  • Use navigator.serviceWorker.register() to register a worker.
  • Use install, activate, and fetch events for caching.
  • Use Cache API with Service Workers for offline app shell and assets.
  • Always use secure caching, cache versioning, and fallback strategies.

Pro Tip

Start simple: cache only your app shell, static assets, and offline page. Add API caching, Background Sync, and push notifications only when your application clearly needs them.