⚡ Prestazioni web

Service worker e strategie di cache: velocità e funzionamento offline

Cache first, network first e stale-while-revalidate spiegate con esempi, più il ciclo di vita del service worker e gli errori che bloccano gli aggiornamenti.

La cache HTTP decide per quanto tempo il browser può riutilizzare un file, ma le regole sono fissate dal server e uguali per tutti. Un service worker offre un controllo molto più fine: è uno script che si interpone tra la pagina e la rete, intercetta ogni richiesta e decide in codice se rispondere dalla cache, dalla rete o da una combinazione delle due. Con lui un sito può caricarsi quasi all'istante alle visite successive e continuare a funzionare quando la connessione cade.

Come si inserisce tra pagina e rete

Il service worker si registra dalla pagina:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

Da quel momento, per tutte le pagine nel suo ambito (di default la cartella in cui si trova il file e le sottocartelle), ogni richiesta passa dall'evento fetch del worker. I file vengono memorizzati con la Cache Storage API, un archivio separato dalla cache HTTP, gestito interamente dal codice.

Due requisiti da ricordare: il service worker funziona solo in HTTPS (con l'eccezione di localhost durante lo sviluppo) e non ha accesso al DOM.

Le strategie principali

Ogni tipo di risorsa merita una strategia diversa.

Cache first

Il worker cerca prima nella cache e va in rete solo se il file manca. È ideale per asset versionati, come app.3f9a1c.js o i font, il cui nome cambia a ogni modifica: una volta in cache non serve mai ricontrollarli.

self.addEventListener('fetch', (evento) => {
  if (evento.request.destination === 'font') {
    evento.respondWith(
      caches.match(evento.request).then(r => r || fetch(evento.request))
    );
  }
});

Network first

Il worker prova la rete e, se fallisce o è troppo lenta, ripiega sulla cache. È adatta all'HTML delle pagine e ai dati che devono essere aggiornati, con la cache come rete di sicurezza per il funzionamento offline. Conviene aggiungere un timeout: una connessione debole che non risponde è peggio di una pagina leggermente vecchia.

Stale-while-revalidate

Il worker risponde subito con la copia in cache e, in parallelo, scarica una versione aggiornata per la volta successiva. L'utente ottiene velocità immediata e i contenuti si aggiornano con una visita di ritardo. Funziona bene per avatar, immagini non critiche, file di configurazione e risposte di API che cambiano raramente.

Risorsa Strategia Motivo
JS e CSS con hash nel nome Cache first Il nome cambia a ogni versione
Font Cache first Cambiano di rado
HTML delle pagine Network first con timeout Contenuto aggiornato, offline di riserva
Immagini dei contenuti Stale-while-revalidate Velocità con aggiornamento in background
Richieste di pagamento o login Solo rete Mai servire risposte vecchie

Il ciclo di vita e gli aggiornamenti

La parte che crea più problemi non sono le strategie, ma gli aggiornamenti. Un service worker attraversa alcune fasi:

  1. install: il nuovo worker viene scaricato e può precaricare in cache i file essenziali;
  2. waiting: se un worker precedente controlla ancora delle schede aperte, il nuovo resta in attesa;
  3. activate: quando le vecchie schede vengono chiuse, il nuovo prende il controllo ed è il momento giusto per eliminare le cache delle versioni precedenti.
const VERSIONE = 'v7';
self.addEventListener('activate', (evento) => {
  evento.waitUntil(
    caches.keys().then(chiavi => Promise.all(
      chiavi.filter(k => k !== VERSIONE).map(k => caches.delete(k))
    ))
  );
});

La fase di attesa spiega un fenomeno comune: «ho pubblicato una correzione ma gli utenti vedono ancora la versione vecchia». Si può forzare il passaggio con skipWaiting(), ma solo se la nuova versione è compatibile con le pagine già aperte; altrimenti è meglio mostrare un avviso «È disponibile un aggiornamento» con un pulsante per ricaricare.

Il file sw.js stesso non dovrebbe essere memorizzato a lungo dalla cache HTTP: i browser lo ricontrollano comunque periodicamente, ma servirlo con una durata breve evita ritardi negli aggiornamenti.

Con la strategia network first per l'HTML c'è un costo nascosto: a ogni navigazione il browser deve prima avviare il service worker, se era inattivo, e solo dopo parte la richiesta di rete. La navigation preload elimina questa attesa: la richiesta parte in parallelo all'avvio del worker, che poi usa la risposta già in arrivo.

self.addEventListener('activate', (e) => {
  e.waitUntil(self.registration.navigationPreload?.enable());
});

Usare una libreria

Scrivere a mano strategie, scadenze, limiti di spazio e pulizie è possibile, ma ripetitivo e soggetto a errori. Workbox è la libreria più diffusa per questo scopo: offre le strategie già pronte, la gestione della scadenza delle voci e l'integrazione con i principali strumenti di build per generare l'elenco dei file da precaricare.

Verificare

In Chrome DevTools, nel pannello Application, la sezione Service workers mostra lo stato del worker, permette di forzare l'aggiornamento e di simulare l'assenza di rete; la sezione Cache Storage elenca i file memorizzati. Nel pannello Network, le risposte servite dal worker sono indicate come tali nella colonna della dimensione.

In sintesi

  • Il service worker intercetta le richieste e decide in codice da dove rispondere.
  • Cache first per asset versionati, network first per l'HTML, stale-while-revalidate per contenuti secondari.
  • Gestisci il ciclo di vita: elimina le cache vecchie in activate e comunica gli aggiornamenti.
  • Attiva la navigation preload per non rallentare le navigazioni.
  • Per progetti reali una libreria come Workbox evita molti errori.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica, unisce il rigore dell'analisi tecnica all'attenzione per le persone che usano la tecnologia.

Leggi anche