My favorite service worker setup

Here is a description of service workers, as depicted in MDN:
Service workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available).They are intended, among other things, to enable the creation of effective offline experiences, intercept network requests, andtake appropriate action based on whether the network is available, and update assets residing on the server. They will also allowaccess to push notifications and background sync APIs.
The first time I set my eyes on service workers, I thought how cool it would be to cache static content as much as I like and not rely on the server. Nowadays, static resources (js, css, images, etc.) are cache busted, meaning that they have a unique name based on their content. So it’s not expected for a static resource to change. So why not cache it forever ?
Also, many of my apps are games, so it would be cool if I could access them offline. The subway means sudoku to me.
Here’s how service workers cover those needs:
- If the request is about a static resource (js, css, images, etc.), the service worker checks if it’s already in the cache. If yes, it returns it, otherwise it fetches it from the server, caches it and then returns it.
- For requests which have to do with retrieving information (mostly GET requests), it passes the request straight to the server, caches the result and returns it. If the request fails though, it checks if the result is in the cache and if it is, it returns it.
This way, I manage to keep static resources cached and provide basic offline experience for static generated content like this blog.
Here’s the snippet of a service-worker.js
file (placed in the public directory):
const cacheName = '<my-cache-name>-v1';
const deleteOldCaches = async () => { const keys = await caches.keys(); await Promise.allSettled( keys.map(async (key) => { if (key !== cacheName) return;
await caches.delete(key); }), );};
self.addEventListener('activate', (event) => { event.waitUntil(deleteOldCaches());});
const cacheFirst = async (event) => { const cache = await caches.open(cacheName); const cachedResponse = await cache.match(event.request); if (cachedResponse) return cachedResponse;
const fetchResponse = await fetch(event.request); if (fetchResponse.status < 300) { cache.put(event.request, fetchResponse.clone()); }
return fetchResponse;};
const networkFirst = async (event) => { const cache = await caches.open(cacheName);
try { const fetchResponse = await fetch(event.request); if (fetchResponse.status < 300) { cache.put(event.request, fetchResponse.clone()); }
return fetchResponse; } catch { const cachedReponse = await cache.match(event.request); if (cachedReponse) { return cachedReponse; } }};
self.addEventListener('fetch', async (event) => { if (!event.request.url.startsWith('http')) return;
const cacheFirstDestinations = [ 'script', 'image', 'font', 'manifest', 'style', ];
if (cacheFirstDestinations.includes(event.request.destination)) { await event.respondWith(cacheFirst(event)); } else if (event.request.method === 'GET') { await event.respondWith(networkFirst(event)); }});
You can register it as follows (in any js
file or page):
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js');}