Skip to main content

Service Worker

What It Is

A service worker is a JavaScript worker that runs between the web application, browser, and network.

It can intercept requests, cache resources, provide offline support, update cached assets, and support features like push notifications and background sync APIs.

Service Worker = Browser proxy layer between app, cache, and network

Why It Matters

Service workers are mainly used to create offline-first and app-like web experiences.

They help the app:

  • load faster after the first visit
  • work even when the network is unavailable
  • reduce repeated network requests
  • serve cached assets instantly
  • clean old cache versions
  • control how requests behave when online or offline
Without Service Worker:
Browser -> Server

With Service Worker:
Browser -> Service Worker -> Cache / Network

Core Behavior

A service worker sits between the browser and the network.

When the browser requests something, the service worker can decide whether to:

  • return it from cache
  • fetch it from the network
  • fallback to an offline page
  • update old cached assets

This makes it useful for PWAs and offline-first systems.


Registering the Service Worker

The sw.js file does not run automatically.

It must be registered from the page using JavaScript, usually inside the main script.js file.

Page loads -> script.js runs -> sw.js gets registered -> service worker installs

Registration Example

// register the service worker from the page so sw.js can install and handle fetches.

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

What This Code Does

PartMeaning
"serviceWorker" in navigatorChecks if the browser supports service workers
window.addEventListener("load")Waits until the page finishes loading
navigator.serviceWorker.register("./sw.js")Registers the service worker file
registration.scopeShows the area controlled by the service worker
.catch()Handles registration failure
script.js connects the page with sw.js.
sw.js then handles install, fetch, activate, cache, and offline behavior.

Service Worker Rules

Service workers have some important behavior rules.

RuleMeaning
Runs in worker contextIt runs separately from the main JavaScript thread
No DOM accessIt cannot directly read or update the page DOM
AsynchronousIt is designed to be non-blocking
No synchronous XHRSynchronous XHR cannot be used inside it
No Web StorageWeb Storage cannot be used inside it
No dynamic importsimport() throws an error in service worker global scope
Static imports allowedStatic import statements are allowed
Secure context requiredWorks on HTTPS or localhost during development

Secure Context

Service workers are only available in secure contexts.

This means the site should be served over HTTPS.

For local development, browsers treat http://localhost as secure.

Production -> HTTPS required
Local development -> localhost allowed

This is important because service workers are powerful. If they were allowed on unsafe HTTP pages, injected malicious code could intercept requests and cache harmful responses.


Lifecycle Events

A basic service worker usually works through three main events:

EventPurpose
installCache important assets before they are needed
fetchIntercept requests and decide cache or network
activateClean old cache versions
Install -> Cache assets
Fetch -> Serve cache or network
Activate -> Remove old cache

Install Event

The install event runs when the service worker is installed for the first time.

The main goal is to cache critical assets before the user needs them.

self.addEventListener("install", (event) => {
event.waitUntil(
caches.open("offline-demo-v1").then((cache) => {
return cache.addAll([
"./index.html",
"./style.css",
"./sample.jpg",
"./script.js",
]);
}),
);
});

What Happens During Install

A versioned cache is created:

caches.open("offline-demo-v1");

Important files are stored locally:

cache.addAll(["./index.html", "./style.css", "./sample.jpg", "./script.js"]);

This means the core application files are available even if the internet disappears later.


Why event.waitUntil Matters

event.waitUntil() tells the browser not to finish installation until the caching operation completes.

Without it, the service worker might be marked as installed before important assets are fully cached.

event.waitUntil() = Wait until cache setup is complete

This helps avoid inconsistent offline behavior.


Fetch Event

The fetch event runs for every network request made by the page.

This includes:

  • HTML
  • CSS
  • images
  • JavaScript
  • API calls

The service worker can intercept each request and decide what to return.

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

return fetch(event.request).catch(() => {
return caches.match("./index.html");
});
}),
);
});

Cache First Strategy

The example service worker uses a Cache First strategy.

It checks the cache first:

caches.match(event.request);

If the response exists in cache, it returns the cached version immediately.

If the response does not exist in cache, it falls back to the network.

Cache First = Cache -> Network fallback

Cache Hit

A cache hit means the requested resource already exists in cache.

Request -> Cache found -> Return cached response

Practical benefit:

  • CSS loads without another network request
  • JS bundle loads faster
  • images can appear instantly
  • bandwidth usage is reduced
  • app feels faster after the first visit

Cache Miss

A cache miss means the requested resource does not exist in cache.

In that case, the service worker fetches it from the network.

return fetch(event.request);
Request -> Cache not found -> Fetch from network

This keeps normal network behavior when the resource has not been cached.


Offline Fallback

If the network request fails completely, the service worker can return a cached fallback.

In the PDF example, failed requests return index.html.

return caches.match("./index.html");

This allows the browser to still render the application shell when offline.

Network failed -> Return cached index.html

This is the core idea behind offline-first web applications.


Important Offline Handling Note

Returning index.html for every failed request is simple, but it is not always correct.

Different request types may need different fallbacks.

Examples:

  • navigation request can return index.html or an offline page
  • image request can return a fallback image
  • API request may need cached JSON
Production service workers usually handle request types separately.

Activate Event

The activate event runs when a new service worker becomes active.

It is mainly used for cleanup.

Over time, an app may create multiple cache versions.

offline-demo-v1
offline-demo-v2
offline-demo-v3

Old caches can cause stale UI, outdated CSS, outdated JavaScript, and unnecessary storage usage.


Activate Event Example

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

This keeps only the current cache version and deletes old ones.


Complete Service Worker Example

/**
* SERVICE WORKER
* Current Strategy: Cache First
* Cache Version: offline-demo-v1
*/

self.addEventListener("install", (event) => {
console.log("Service Worker installing...");

event.waitUntil(
caches
.open("offline-demo-v1")
.then((cache) => {
console.log("Caching core application assets...");

return cache.addAll([
"./index.html",
"./style.css",
"./sample.jpg",
"./script.js",
]);
})
.catch((err) => {
console.error("Cache installation failed:", err);
}),
);
});

self.addEventListener("fetch", (event) => {
console.log("Fetching:", event.request.url);

event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
console.log("Serving from cache:", event.request.url);
return response;
}

return fetch(event.request).catch(() => {
console.log("Network failed. Serving offline fallback.");
return caches.match("./index.html");
});
}),
);
});

self.addEventListener("activate", (event) => {
console.log("Service Worker activating...");

event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== "offline-demo-v1") {
console.log("Deleting old cache:", cacheName);
return caches.delete(cacheName);
}
}),
);
}),
);
});

Overall Flow

The service worker in this note implements a simple offline-first architecture.

1. Install phase caches important assets
2. Fetch phase intercepts browser requests
3. Cached files are served whenever possible
4. Network is used as fallback
5. Offline fallback is returned if network fails
6. Activate phase cleans old cache versions

Result:

  • faster reloads
  • offline support
  • reduced network dependency
  • app-like behavior
  • cleaner cache version management

When to Use Cache First

Cache First works well for mostly static assets.

Good examples:

  • CSS files
  • JavaScript bundles
  • logos
  • images
  • app shell files

These files usually do not change on every request, so serving them from cache improves speed.


Service Worker Table

PartPurpose
Service WorkerProxy layer between app, cache, and network
installPre-cache important assets
fetchIntercept requests and return cache or network
activateDelete old cache versions
Cache FirstServe from cache first, network second
Offline FallbackReturn cached page when network fails
Versioned CachePrevent stale assets and manage updates
Secure ContextService workers require HTTPS or localhost

Basic Checklist

Create a versioned cache name
Cache core assets during install
Use event.waitUntil during install
Intercept requests using fetch event
Check cache first using caches.match
Fetch from network when cache is missing
Return offline fallback when network fails
Clean old cache versions during activate
Use HTTPS in production
Handle different request types carefully in production

Interview Style Answer

A service worker is an event-driven JavaScript worker that acts like a proxy between the web application, browser, cache, and network. It can intercept requests, cache resources, provide offline support, update cached assets, and support features like push notifications and background sync. It runs in a separate worker context, has no DOM access, is asynchronous, and requires a secure context such as HTTPS or localhost. A common offline-first setup uses the install event to pre-cache important assets, the fetch event to serve cached responses first and fall back to the network, and the activate event to remove old cache versions. This improves reload speed, reduces network dependency, and allows the app shell to work even when the network is unavailable.


One-Line Summary

Service Worker = A secure background proxy that caches assets, intercepts requests, and enables offline-first web apps.

Final Mental Model

Install -> Save important files
Fetch -> Cache first, network second
Offline -> Return fallback
Activate -> Remove old cache
Result -> Faster loading + offline support