// apis · Web Platform Advent #4
Service Worker: caching, offline e aggiornamenti spiegati
Una guida pratica ai service worker — registrarne uno, il ciclo di vita install/activate, la Cache API, servire una pagina offline e distribuire aggiornamenti in sicurezza.
Un service worker è uno script che il browser esegue in background, separato dalla tua pagina. Si colloca tra la tua app e la rete come un proxy programmabile: può intercettare le richieste, rispondere dalla cache e mantenere il tuo sito funzionante anche quando l'utente è offline. È la base delle Progressive Web App.
Due regole fondamentali prima di tutto. Un service worker funziona solo su HTTPS (o localhost durante lo sviluppo) e non ha accesso al DOM — comunica con le pagine tramite eventi e messaggi, non toccando document.
Registrare un service worker
Registri il worker dal normale script della tua pagina. Verifica prima la disponibilità della funzionalità, poi indirizza il browser al file dello script:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js')
.then((reg) => console.log('Scope:', reg.scope))
.catch((err) => console.error('SW failed:', err));
});
} La posizione del file è importante: un worker in /sw.js controlla l'intero origin, mentre uno in /app/sw.js controlla solo le pagine sotto /app/. Quel confine è il suo scope.
Il ciclo di vita: install e activate
Un service worker ha una propria vita indipendente dalla pagina. I due eventi che ti interessano di più sono install (attivato una sola volta, un buon punto per pre-mettere in cache gli asset) e activate (un buon punto per ripulire le vecchie cache):
const CACHE = 'site-v1';
const ASSETS = ['/', '/index.html', '/styles.css', '/app.js', '/offline.html'];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
}); Chiamare event.waitUntil() dice al browser di non considerare la fase conclusa finché la tua promise non si risolve — altrimenti potrebbe terminare il worker a metà del compito.
Intercettare le richieste con fetch
L'evento fetch è dove avviene la magia. Decidi tu cosa restituisce ogni richiesta. Una strategia comune e sicura è cache-first con fallback sulla rete:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request);
})
);
}); Per mostrare una pagina offline personalizzata quando sia la cache sia la rete falliscono, aggiungi un catch:
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).catch(() => caches.match('/offline.html'))
);
}); Strategie di caching a colpo d'occhio
| Strategia | Comportamento | Adatta per |
|---|---|---|
| Cache-first | Servire dalla cache, ripiegare sulla rete | Asset statici (CSS, JS, font) |
| Network-first | Provare la rete, ripiegare sulla cache | HTML, dati API che cambiano |
| Stale-while-revalidate | Servire dalla cache, aggiornarla in background | Avatar, feed, elenchi di contenuti |
Distribuire aggiornamenti senza penalizzare gli utenti
Quando modifichi sw.js, il browser rileva i nuovi byte e installa il nuovo worker, ma rimane in attesa finché ogni scheda che usa il vecchio non viene chiusa. Questo impedisce che due versioni vengano eseguite contemporaneamente. Per prendere il controllo prima, chiama self.skipWaiting() in install e clients.claim() in activate — ma fallo solo quando sei certo che la nuova versione sia compatibile con le pagine già aperte.
Incrementa sempre il nome della cache (site-v1 → site-v2) quando gli asset cambiano. L'handler activate qui sopra elimina poi la cache obsoleta, così gli utenti non ricevono mai un misto di file vecchi e nuovi.
Questo è l'intero ciclo fondamentale: registrare, pre-mettere in cache all'install, ripulire all'activate e rispondere alle richieste al fetch. Inizia con una strategia cache-first per i file statici e una network-first per il tuo HTML, e avrai un sito che si carica istantaneamente alle visite ripetute e sopravvive a una connessione caduta.