I service worker sono una delle tecnologie più potenti a disposizione di un sito web, e una delle più fraintese. Permettono di far funzionare un sito anche senza connessione, di rendere istantanee le visite successive, di ricevere notifiche. Sono la base delle Progressive Web App. Ma un service worker scritto male, o usato senza una ragione precisa, può rendere il sito più lento, mostrare contenuti vecchi agli utenti e diventare una fonte di problemi difficili da diagnosticare.
In questo articolo vediamo cos’è un service worker, quando porta benefici reali alle prestazioni, quali sono i costi nascosti e come introdurlo senza sorprese in un sito WordPress.
Cos’è un service worker
Un service worker è uno script JavaScript che il browser registra per un sito e che si interpone tra le pagine e la rete. Ogni volta che una pagina del sito chiede una risorsa, un documento HTML, un foglio di stile, un’immagine, una chiamata a un’interfaccia di programmazione, la richiesta passa dal service worker, che può decidere cosa fare: inoltrarla alla rete, rispondere con una copia salvata in una cache dedicata, o combinare le due cose.
Il service worker vive separato dalle pagine: non ha accesso al DOM, gira su un proprio thread e il browser lo avvia quando serve e lo ferma quando è inattivo. Una volta installato, resta attivo anche dopo che l’utente ha chiuso il sito, pronto a intercettare le richieste alla visita successiva. È questa persistenza a renderlo potente, e a rendere importanti gli errori.
La cache usata dal service worker è distinta dalla normale cache HTTP del browser, quella governata dalle intestazioni Cache-Control e dalle risposte 304. La cache HTTP è gestita dal browser secondo regole standard; la cache del service worker è gestita interamente dal codice del sito, che decide cosa salvare, per quanto tempo e quando aggiornarlo.
Le strategie di cache
Il comportamento di un service worker si descrive con alcune strategie, da scegliere per ogni tipo di risorsa.
- prima la cache: se la risorsa è in cache viene restituita subito, altrimenti si scarica e si salva. È ideale per i file statici con un numero di versione nel nome, come
style.3f9a.css, che non cambiano mai: quando cambiano, cambia il nome; - prima la rete: si tenta la rete, e solo se fallisce si usa la copia in cache. È adatta alle pagine HTML e ai dati che devono essere aggiornati, con il vantaggio di avere una copia di riserva quando la connessione manca;
- cache, poi aggiorna: si risponde subito con la copia in cache e intanto si scarica la versione aggiornata per la visita successiva. È un compromesso utile per risorse che cambiano ma per cui una versione leggermente vecchia è accettabile, come avatar o immagini di anteprima;
- solo rete: il service worker non interviene. È la scelta giusta per le richieste che non devono mai essere servite da una cache, come il carrello, il pagamento, l’area riservata, le chiamate di tracciamento.
Dove migliora davvero le prestazioni
Il beneficio principale riguarda le visite successive. Con i file statici in cache e una strategia che prima cerca in cache, il browser non deve nemmeno verificare con il server se le risorse sono cambiate: le trova localmente e le usa. Per un sito che viene visitato spesso dagli stessi utenti, come un’applicazione web, un gestionale, un servizio in abbonamento, un sito di notizie letto ogni giorno, le visite successive possono diventare molto più rapide, soprattutto su reti mobili instabili.
Il secondo beneficio è la resilienza. Su una connessione intermittente, come in treno o in una zona con copertura scarsa, un service worker può mostrare una pagina salvata o una pagina di cortesia al posto dell’errore del browser. Per alcuni siti, come le applicazioni usate sul campo o i cataloghi consultati in negozio, questa è la ragione principale per adottarlo.
È importante però essere realistici: un service worker non migliora la prima visita, che è quella che conta per molti siti e per gran parte degli utenti che arrivano dai motori di ricerca o dalle campagne. Alla prima visita il service worker non esiste ancora: viene scaricato, installato e attivato, e inizia a intercettare le richieste solo dalle pagine successive. Anzi, se l’installazione scarica subito molti file da salvare in cache, può competere per la banda con le risorse della pagina che l’utente sta guardando.
Il costo nascosto: l’avvio
C’è un costo di cui si parla poco. Quando l’utente apre una pagina del sito, se il service worker non è già in esecuzione il browser deve avviarlo prima di poter gestire la richiesta: caricarne il codice, eseguirlo, attendere che risponda all’evento della richiesta. Su un computer veloce l’avvio richiede pochi millisecondi; su uno smartphone di fascia bassa può richiedere decine o centinaia di millisecondi. Se il service worker poi inoltra la richiesta della pagina alla rete, quel tempo si aggiunge interamente al TTFB.
Il paradosso è che un service worker che non fa nulla di utile per le pagine HTML, per esempio uno che si limita a inoltrare tutte le richieste alla rete, peggiora le prestazioni senza offrire alcun vantaggio. È una situazione frequente: plugin o temi che registrano un service worker per le notifiche o per una funzione secondaria, con un gestore delle richieste che intercetta tutto.
Ci sono due rimedi. Il primo è il navigation preload: un’opzione che dice al browser di avviare la richiesta della pagina alla rete in parallelo all’avvio del service worker, così che il tempo di avvio non si sommi. Il service worker riceve poi la risposta già in arrivo e la usa. È la configurazione raccomandata per qualunque service worker che gestisca le navigazioni con una strategia che prima consulta la rete.
self.addEventListener( 'activate', ( event ) => {
event.waitUntil( self.registration.navigationPreload?.enable() );
} );
self.addEventListener( 'fetch', ( event ) => {
if ( event.request.mode !== 'navigate' ) {
return;
}
event.respondWith( ( async () => {
try {
return ( await event.preloadResponse ) || ( await fetch( event.request ) );
} catch ( e ) {
return caches.match( '/offline/' );
}
} )() );
} );
Il secondo rimedio è non intercettare ciò che non serve. Un gestore delle richieste che esce subito per le risorse che non deve trattare è molto meglio di uno che le inoltra tutte. Le versioni recenti di Chrome permettono anche di dichiarare, al momento dell’installazione, regole che fanno andare direttamente in rete alcune richieste senza avviare il service worker.
Gli errori più comuni
Mettere in cache l’HTML con la strategia sbagliata. Se le pagine vengono servite prima dalla cache, gli utenti vedono versioni vecchie: un prezzo cambiato, un articolo corretto, un avviso rimosso. Peggio ancora se la pagina contiene informazioni personali, come il nome dell’utente collegato o il numero di prodotti nel carrello, che finiscono in cache e vengono mostrate in modo incoerente. Per l’HTML la strategia più sicura è quasi sempre prima la rete, con la cache come riserva.
Non gestire gli aggiornamenti. Quando si pubblica una nuova versione del sito, il nuovo service worker viene scaricato ma, per impostazione predefinita, entra in funzione solo quando tutte le schede del sito sono state chiuse. Nel frattempo gli utenti usano la versione vecchia, con i file vecchi in cache. Se i file statici non hanno un numero di versione nel nome, si rischia di mescolare CSS nuovo e JavaScript vecchio. La soluzione è dare sempre una versione ai file statici, eliminare le cache delle versioni precedenti all’attivazione e decidere consapevolmente come gestire il passaggio.
Salvare troppo all’installazione. Precaricare decine di file e immagini alla prima visita consuma banda e dati mobili dell’utente, compete con il caricamento della pagina corrente e occupa spazio sul dispositivo. Conviene precaricare solo l’essenziale, registrare il service worker dopo il caricamento della pagina e salvare il resto man mano che viene usato.
Dimenticare di poterlo rimuovere. Un service worker difettoso resta installato nei browser degli utenti anche dopo che il file è stato corretto, finché il browser non verifica gli aggiornamenti. Conviene sapere in anticipo come disattivarlo: pubblicando un service worker che si deregistra e svuota le proprie cache, oppure usando l’intestazione Clear-Site-Data. E conviene servire il file del service worker senza una lunga durata di cache, così che il browser trovi rapidamente le versioni nuove.
Service worker e altre tecniche
Molti dei benefici che un tempo richiedevano un service worker oggi si ottengono con strumenti più semplici. Una buona politica di cache HTTP, con file statici versionati e una durata lunga, fa sì che le visite successive trovino già la maggior parte delle risorse, senza codice aggiuntivo. La back/forward cache rende istantanei i ritorni alle pagine precedenti. Le speculation rules preparano in anticipo le pagine che l’utente probabilmente visiterà. Un service worker ha senso quando serve qualcosa che questi strumenti non danno: il funzionamento offline, il controllo preciso di cosa viene salvato, le notifiche, un’esperienza da applicazione.
Un service worker non impedisce l’uso della back/forward cache e convive con le speculation rules, ma le pagine prerenderizzate passano comunque dal service worker: un gestore lento rallenta anche quelle.
In WordPress
Nei siti WordPress i service worker arrivano di solito da plugin: plugin per le Progressive Web App, per le notifiche push, alcuni plugin di cache e alcuni temi. Prima di tutto conviene sapere se il sito ne ha uno: nei DevTools di Chrome, nella scheda Application, la sezione dedicata mostra i service worker registrati, il loro stato e le cache che usano. Se ce n’è uno, vale la pena capire quale plugin lo registra, quali richieste intercetta e con quale strategia.
Per chi vuole introdurlo, la strada più sicura è partire da una libreria consolidata come Workbox, che implementa le strategie descritte e gestisce versioni e aggiornamenti, e da una configurazione minima: file statici con prima la cache, pagine HTML con prima la rete e navigation preload, una pagina offline, nessuna intercettazione per le aree dinamiche come carrello, pagamento, account e amministrazione.
Come misurare
Gli effetti di un service worker si vedono soprattutto nei dati reali, perché riguardano le visite successive, che i test di laboratorio simulano male. Un sistema di monitoraggio degli utenti reali che distingua le pagine servite con e senza service worker, o le prime visite dalle successive, permette di confrontare TTFB e LCP nei due casi. In laboratorio, il pannello Performance mostra l’avvio del service worker e il tempo speso nel suo gestore; la scheda Rete indica quali risorse sono state servite dal service worker. Un TTFB che peggiora dopo l’introduzione di un service worker è il segnale tipico di un avvio non compensato dal navigation preload.
In sintesi
Un service worker può rendere istantanee le visite successive e far funzionare il sito senza connessione, ma non migliora la prima visita e introduce un costo di avvio che, se non gestito, peggiora il TTFB. Funziona bene quando si scelgono strategie diverse per risorse diverse, si abilita il navigation preload, si evita di intercettare ciò che non serve, si gestiscono versioni e aggiornamenti e si precarica solo l’essenziale. Prima di adottarlo conviene chiedersi se il problema da risolvere non sia già coperto da una buona cache HTTP, dalla back/forward cache o dalle speculation rules; e, se il sito ne ha già uno, verificare che stia aiutando e non rallentando.