JavaScript Service Workers

A Service Worker is a JavaScript script that runs in the background — separate from the main browser page — and acts as a programmable network proxy. It intercepts every network request made by the page and decides whether to fetch it from the network, serve it from a local cache, or return something custom. This is what makes Progressive Web Apps (PWAs) work offline.

What Makes Service Workers Special

A service worker runs on its own thread. It has no access to the DOM and cannot directly talk to the page — but it can intercept fetch requests, manage a cache, and listen for push notifications. Even when the user closes the tab, a service worker can keep running in the background.

Diagram: Service Worker Position

Without Service Worker:
  Page ──── fetch("/data") ────────────► Network
  Page ◄─── response ◄─────────────── Network

With Service Worker:
  Page ──── fetch("/data") ───────────► Service Worker
                                            │
                                   Cached? YES → return cache
                                   Cached? NO  → fetch from Network
                                            │
  Page ◄─── response ◄────────────────────┘

Service Worker Lifecycle

Diagram: Lifecycle States

Register → Download → Install → Activate → Idle
                          │
                     (cache assets)
                          │
                     (update check)
                          │
                   Waiting (if old SW exists)
                          │
                   Activate (old SW terminated)
                          │
                   Fetch / Sync / Push events

Step 1: Register the Service Worker

Register the service worker from your main JavaScript file. Registration tells the browser to download and install the service worker script.

// In your main app JS file (e.g., app.js)
if ("serviceWorker" in navigator) {
  window.addEventListener("load", function() {
    navigator.serviceWorker.register("/sw.js")
      .then(function(registration) {
        console.log("Service Worker registered:", registration.scope);
      })
      .catch(function(error) {
        console.log("Registration failed:", error);
      });
  });
}

The check "serviceWorker" in navigator ensures older browsers that don't support it won't throw errors.

Step 2: Install and Cache Assets

The install event fires when the service worker installs. Use it to pre-cache the files your app needs to work offline.

// sw.js — the Service Worker file

const CACHE_NAME = "my-app-v1";
const FILES_TO_CACHE = [
  "/",
  "/index.html",
  "/style.css",
  "/app.js",
  "/images/logo.png"
];

self.addEventListener("install", function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      console.log("Caching app shell files...");
      return cache.addAll(FILES_TO_CACHE);
    })
  );
});

Diagram: Install Event — Pre-Caching

install event fires
  │
  caches.open("my-app-v1")
  │
  cache.addAll(["/", "/index.html", "/style.css", ...])
  │
  Browser downloads and stores all files locally
  │
  App now works offline for these files

Step 3: Activate and Clean Up Old Caches

self.addEventListener("activate", function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames
          .filter(name => name !== CACHE_NAME)
          .map(name => {
            console.log("Deleting old cache:", name);
            return caches.delete(name);
          })
      );
    })
  );
});

Step 4: Intercept Fetch Requests

The fetch event fires for every network request. This is where you serve cached responses or fall back to the network.

Cache First Strategy (Best for Offline)

self.addEventListener("fetch", function(event) {
  event.respondWith(
    caches.match(event.request).then(function(cachedResponse) {
      if (cachedResponse) {
        return cachedResponse; // serve from cache
      }
      return fetch(event.request); // not in cache — fetch from network
    })
  );
});

Diagram: Cache First Strategy

Request arrives at Service Worker
        │
  Is it in cache?
        │
  YES ──┤──► Return cached version (fast, offline works)
        │
  NO   ──┤──► Fetch from network
               │
          Return network response
               │
         (optionally cache it for next time)

Network First Strategy (Best for Fresh Data)

self.addEventListener("fetch", function(event) {
  event.respondWith(
    fetch(event.request)
      .then(function(networkResponse) {
        // Got it from network — update the cache
        let responseClone = networkResponse.clone();
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, responseClone);
        });
        return networkResponse;
      })
      .catch(function() {
        // Network failed — serve from cache
        return caches.match(event.request);
      })
  );
});

Diagram: Network First Strategy

Request arrives
  │
  Try network first
  │
  Network OK? YES ──► return response, update cache
  │
  Network FAIL? ──────► serve from cache (offline fallback)

Background Sync

Background sync lets a service worker retry failed requests when the connection comes back — useful for offline form submissions.

// In the main app — register a sync when sending a message
navigator.serviceWorker.ready.then(function(registration) {
  registration.sync.register("send-message");
});

// In sw.js — handle the sync event
self.addEventListener("sync", function(event) {
  if (event.tag === "send-message") {
    event.waitUntil(sendPendingMessages());
  }
});

function sendPendingMessages() {
  // Retrieve queued messages from IndexedDB and send them
  return fetch("/api/messages", { method: "POST", body: "..." });
}

Common Caching Strategies

StrategyHow It WorksBest For
Cache FirstCache → Network (fallback)Static assets, app shell
Network FirstNetwork → Cache (fallback)API data, news feeds
Cache OnlyAlways serve from cacheFully offline apps
Network OnlyAlways fetch from networkReal-time data, analytics
Stale While RevalidateServe cache instantly, update in backgroundAvatars, config files

Limitations of Service Workers

  • Only work over HTTPS (or localhost for development).
  • Cannot access the DOM directly.
  • Run on a separate thread — communicate with the page via postMessage.
  • Cache grows — you must manage and version it to prevent stale data.

Checking Service Worker Support

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

Summary

Service Workers sit between your app and the network, intercepting every request. During install, they pre-cache essential files. During activate, they clean up old caches. During fetch, they choose whether to serve from cache or the network based on your chosen strategy. They are the engine behind offline-capable Progressive Web Apps, background sync, and push notifications — making web apps feel fast and reliable even with a poor connection.

Leave a Comment

Your email address will not be published. Required fields are marked *