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
| Part | Meaning |
|---|---|
"serviceWorker" in navigator | Checks 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.scope | Shows 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.
| Rule | Meaning |
|---|---|
| Runs in worker context | It runs separately from the main JavaScript thread |
| No DOM access | It cannot directly read or update the page DOM |
| Asynchronous | It is designed to be non-blocking |
| No synchronous XHR | Synchronous XHR cannot be used inside it |
| No Web Storage | Web Storage cannot be used inside it |
| No dynamic imports | import() throws an error in service worker global scope |
| Static imports allowed | Static import statements are allowed |
| Secure context required | Works 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:
| Event | Purpose |
|---|---|
| install | Cache important assets before they are needed |
| fetch | Intercept requests and decide cache or network |
| activate | Clean 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.htmlor 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
| Part | Purpose |
|---|---|
| Service Worker | Proxy layer between app, cache, and network |
| install | Pre-cache important assets |
| fetch | Intercept requests and return cache or network |
| activate | Delete old cache versions |
| Cache First | Serve from cache first, network second |
| Offline Fallback | Return cached page when network fails |
| Versioned Cache | Prevent stale assets and manage updates |
| Secure Context | Service 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