Service workers are the engine behind Progressive Web Apps. They are JavaScript files that run in a separate thread from your web page, acting as a programmable network proxy between your app and the server.
How Service Workers Differ from Regular JavaScript
| Property | Web Page JS | Service Worker |
|---|---|---|
| Thread | Main thread | Separate background thread |
| DOM access | Yes | No |
| Lifecycle | Tied to page | Independent of page |
| Network access | Direct | Intercepts all requests |
| HTTPS required | No | Yes (except localhost) |
| Persistent storage | localStorage, IndexedDB | Cache API, IndexedDB |
Registration
Register the service worker from your main JavaScript file:
Scope
The scope determines which pages the service worker controls. By default, it is the directory where sw.js lives.
The Install Event
The install event fires when the browser detects a new or updated service worker. This is where you pre-cache your app shell.
event.waitUntil() tells the browser to keep the service worker alive until the promise resolves. If the promise rejects, the installation fails.
The Activate Event
The activate event fires after installation, when the service worker takes control. Use this to clean up old caches.
skipWaiting and clients.claim
By default, a new service worker waits until all tabs using the old one are closed. These methods bypass that:
self.skipWaiting()— activates the new worker immediatelyself.clients.claim()— takes control of all open pages without a reload
The Fetch Event
This is where the magic happens. The service worker intercepts every network request from controlled pages.
Why clone the response?
A response body can only be read once. If you want to cache the response and also return it to the page, you must clone it:
const response = await fetch(request);
cache.put(request, response.clone()); // clone for cache
return response; // original for page
The Cache API
The Cache API stores request-response pairs.
Updating Service Workers
When you change your sw.js file (even one byte), the browser treats it as a new version.
Update Flow
- Browser fetches
sw.jsand detects changes - New service worker installs alongside the old one
- New worker enters "waiting" state
- When all tabs using the old worker close, the new one activates
Versioned Caches
Use version numbers in cache names so the activate event can clean up old caches:
Communicating with the Page
Service workers and pages can exchange messages:
Page to Service Worker
Service Worker to Page
Debugging Service Workers
Use Chrome DevTools:
- Open Application tab
- Click Service Workers in the sidebar
- See registered workers, their state, and controls to:
- Update — force check for a new version
- Unregister — remove the service worker
- skipWaiting — activate a waiting worker
Check Cache Storage to see what is cached and Network tab to see which requests the service worker intercepted.
Practical Exercise
Build a service worker with proper versioning and cleanup:
Key Takeaways
- Service workers run in a background thread with no DOM access but full control over network requests.
- The lifecycle (install, activate, fetch) gives you precise control over caching and updates.
- Always clone responses before caching — a response body can only be consumed once.
- Use versioned cache names and clean up old caches during activation.
skipWaiting()andclients.claim()let new service workers take over immediately.