What a service worker is and how it differs from ordinary JavaScript

Ordinary JavaScript runs on the page’s main thread and has access to the DOM. A service worker runs on a separate background thread — it has no DOM access, but it can intercept every network request and manage the browser cache.

The service worker lifecycle:

  1. Registration — the page registers it with navigator.serviceWorker.register('/sw.js')
  2. Installation — the worker installs and may pre-cache static resources
  3. Activation — the worker takes control of the page
  4. Operation — it intercepts requests through the fetch event
// sw.js - a basic cache-first strategy
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request);
    })
  );
});

Caching strategies

A service worker lets you apply different strategies to different resource types:

Strategy Logic Where it fits
Cache first Cache first, then the network Static assets (CSS, JS, fonts)
Network first Network first, cache on failure HTML pages, data
Stale while revalidate Serve the cache, refresh in the background Images, semi-static content
Network only Always the network, never the cache Personalization APIs, the basket
Cache only The cache alone Offline fallbacks

Service workers and personalization: the pitfalls

For an e-commerce site with personalization, a service worker creates one specific risk: a cached personalized response. If the recommendations API lands in the worker cache with a long TTL, another visitor or the next session receives someone else’s recommendations.

Rules for personalized data:

  1. Exclude from the cache every request carrying an Authorization header or a userId parameter
  2. Network only for the recommendations, basket and profile APIs
  3. Cache only for fully static resources alone
// Excluding the personalization API from caching
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  if (url.pathname.startsWith('/api/personalization/')) {
    // Always over the network - never cached
    event.respondWith(fetch(event.request));
    return;
  }
  // Everything else - cache first
  event.respondWith(cacheFirst(event.request));
});

Use in an e-commerce PWA

  • An offline page — a visitor with no connection sees a custom page rather than a browser error
  • Prefetching — the worker loads the next page’s resources while the visitor reads the current one
  • Web push — notifications about an abandoned basket, a price drop, new arrivals
  • Background sync — adding to a wishlist offline and syncing once the connection returns

Important: every site deploy needs the service worker cache invalidated. Visitors can keep seeing the old version until the worker updates. Use versioned cache keys, or call skipWaiting() for critical updates.