Suggest an editImprove this articleRefine the answer for “What is service worker?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Service worker** - a background JavaScript script running on a separate thread that intercepts network requests and enables offline functionality for web apps. ```javascript navigator.serviceWorker.register('/sw.js'); ``` **Key:** no DOM access, but full control over caching and network requests. The foundation for Progressive Web Apps (PWAs).Shown above the full answer for quick recall.Answer (EN)Image**Service worker** - a JavaScript script that runs on its own background thread, separate from your web page, intercepting every network request your app makes. ## Theory ### TL;DR - A service worker sits between your page and the network, like a proxy you control with code - It runs on its own thread, has no DOM access, and persists after the page closes - The browser fires three events in order: `install` (cache assets), `activate` (clean old caches), `fetch` (intercept requests) - Use it when you need offline support, faster repeat visits, push notifications, or background sync - Not needed for simple static sites or server-rendered apps without offline requirements ### Quick example ```javascript // 1. Register in your main app file if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(reg => console.log('SW registered')) .catch(err => console.log('SW failed:', err)); } // 2. Inside sw.js - return cached response or go to network self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) // Cached version first, network as fallback ); }); ``` Two files: the registration code runs on your page, the worker logic runs in `sw.js` on its own thread. ### Separate thread, no DOM Service workers run off the main thread. They never block UI rendering, no matter what they do. But that also means no `document`, no `window`, no `localStorage`. Try to access `document` inside a service worker and you get a `ReferenceError`. To pass data between the worker and the page, use `postMessage()`. The worker sends a message, the page listens and updates the DOM. That is the only communication channel. ### Lifecycle Before a service worker handles any requests, it goes through three phases: 1. **`install`** - fires once when the browser first downloads the script. Pre-cache assets here using `event.waitUntil()`. If caching fails, the install fails and the worker does not activate. 2. **`activate`** - fires after install, once the old service worker is gone. Clean up stale caches here so users do not get stuck with outdated files. 3. **`fetch`** - fires on every network request from the page. This is where you decide: serve from cache, fetch from network, or a mix of both. After activation, the service worker stays alive in the background even when the page is closed. That is what makes push notifications and background sync possible. ### When to use - **Progressive Web Apps (PWAs):** need offline mode or an installable app experience - **Performance:** cache static assets (CSS, JS, images) so repeat visits skip the network entirely - **Unreliable networks:** mobile users who drop connectivity mid-session - **Background sync:** queue form submissions while offline, send them when the connection returns - **Push notifications:** show alerts even when the browser tab is closed Skip service workers for simple marketing sites, fully server-rendered apps, or anything that does not need offline support. ### Caching strategies Two patterns cover most real apps. **Cache-first** for static assets: check cache, return immediately, fall back to network if not found. Fast. Good for CSS, JS, and fonts that rarely change. **Network-first** for API calls: hit the network first, store the response in cache, fall back to cache if offline. Keeps data fresh. Good for user profiles, feeds, and any dynamic content. You apply these per request type, not globally. A real `sw.js` runs both strategies inside the same `fetch` handler. ### Common mistakes **1. Not versioning caches** The browser caches the service worker script itself. Update `/sw.js` and users keep the old version until they close every open tab and revisit. The fix is simple: bump the cache version on every deploy. ```javascript // Wrong: cache name never changes, users are stuck const CACHE_NAME = 'app-cache'; // Right: increment version with each release const CACHE_NAME = 'app-v2'; self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(names => Promise.all(names.map(name => name !== CACHE_NAME && caches.delete(name) )) ) ); }); ``` **2. Caching API responses without thinking about stale data** Cache a user profile and it sits there until you explicitly clear it. The server updates the profile, the user still sees old data. Use network-first for anything that changes server-side. Cache-first belongs only on static assets. **3. Trying to access the DOM** ```javascript // Wrong: ReferenceError inside a service worker self.addEventListener('fetch', event => { document.body.innerHTML = 'Offline'; // ReferenceError }); // Right: send a message to the page instead self.clients.matchAll().then(clients => { clients.forEach(client => client.postMessage({ status: 'offline' })); }); ``` **4. Ignoring the HTTPS requirement** Service workers only run on HTTPS. Locally, `localhost` and `127.0.0.1` work without it. Deploy to plain HTTP in production and the service worker does not register at all. Always confirm your production environment uses HTTPS before debugging anything else. **5. Expecting instant updates** New service worker code does not activate while the user has any tab open. If you need immediate activation (risky), call `self.skipWaiting()` in `install` and `clients.claim()` in `activate`. Do this only if the new version is fully compatible with any data already in the cache. ### Real-world usage - **Workbox (Google):** wraps caching strategies into a clean API; used by Gatsby, Create React App, and Firebase Hosting - **Next.js:** the `next-pwa` plugin adds service worker support for static exports - **Notion, Figma, Google Docs:** cache documents locally and sync changes when connection returns - **Twitter / X:** queues posts while offline, sends them when connection returns - **Shopify PWAs:** cache product images and checkout pages for faster load times I have seen teams skip the cache version bump once and spend an afternoon figuring out why half their users saw a broken layout. Automating the version increment in your CI/CD pipeline saves that headache entirely. ### Follow-up questions **Q:** What is the difference between a service worker and a web worker? **A:** Web workers run on a separate thread but are tied to a single page and stop when it closes. Service workers persist across page closes, handle requests from multiple tabs, and intercept network traffic. Web workers are for CPU-heavy computation; service workers are for caching and offline support. **Q:** Can a service worker access `localStorage`? **A:** No. `localStorage` is main-thread only. Use IndexedDB instead. It works in service workers, handles larger data (50MB+ vs 5-10MB), and is designed for async access. **Q:** How do you update a service worker without breaking the user experience? **A:** Version your cache names, clean up old caches in `activate`, and show a "new version available" banner that reloads on user click. Use `skipWaiting()` only if you are confident the new version is backward-compatible with existing cached data. **Q:** What happens if a service worker throws an error? **A:** The browser catches it, the worker stops, and requests fall through to the network. The registration stays intact. Check DevTools > Application > Service Workers for error logs. **Q:** (Senior) How would you handle offline edits that conflict with server changes after a user reconnects hours later? **A:** Store every offline change in IndexedDB with a timestamp and change ID. On reconnect, diff local changes against the server version and apply a conflict strategy. Last-write-wins is simple but destructive. Field-level merging is more complex but safer. Libraries like Automerge and Yjs use CRDTs (Conflict-free Replicated Data Types) to resolve conflicts automatically. The hard part is showing the merge result in the UI without confusing the user. ## Examples ### Basic: register and serve from cache ```javascript // In your main app file if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } // sw.js const CACHE = 'app-v1'; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE).then(cache => cache.addAll(['/index.html', '/styles.css', '/app.js']) ) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request).then(cached => cached || fetch(event.request)) ); }); ``` Static assets go into cache on install. Every request checks the cache first, falls back to the network if not found. ### Intermediate: cache-first for assets, network-first for API ```javascript const CACHE_NAME = 'app-v2'; self.addEventListener('fetch', event => { const { request } = event; // Cache-first: static files load instantly on repeat visits if (request.url.includes('/static/')) { event.respondWith( caches.match(request).then(cached => cached || fetch(request)) ); return; } // Network-first: API responses stay fresh, cache covers offline if (request.url.includes('/api/')) { event.respondWith( fetch(request) .then(response => { const copy = response.clone(); caches.open(CACHE_NAME).then(cache => cache.put(request, copy)); return response; }) .catch(() => caches.match(request)) ); } }); ``` One `fetch` handler, two strategies. Static files never wait for the network. API calls always try for fresh data but survive being offline. ### Advanced: stale-while-revalidate ```javascript // Return cached response immediately, update cache in background self.addEventListener('fetch', event => { if (event.request.method !== 'GET') return; event.respondWith( caches.open(CACHE_NAME).then(cache => { return cache.match(event.request).then(cached => { const networkFetch = fetch(event.request).then(response => { if (response.status === 200) { cache.put(event.request, response.clone()); } return response; }); // Cached content loads instantly; fresh version saves to cache in background return cached || networkFetch; }); }) ); }); ``` The user sees content immediately. In the background, the worker fetches a fresh version and stores it. The next visit gets the updated content. Good balance between speed and freshness for pages that change occasionally.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.