navigator.sendBeacon vs fetch keepalive: consegna affidabile dei dati analytics senza bloccare l'unload
Come inviare payload di analytics che sopravvivono allo scaricamento della pagina senza rallentare la navigazione — e perché la scelta tra sendBeacon e fetch keepalive conta per una raccolta dati senza cookie.
Registrare una visualizzazione di pagina o un evento è semplice finché la pagina è attiva. La parte difficile è consegnare quell'ultimo payload proprio mentre l'utente naviga altrove, chiude la scheda o mette l'app in background su mobile. Sbagliare significa perdere dati oppure, peggio, ritardare proprio la navigazione che l'utente ha richiesto. Questo articolo confronta le due opzioni credibili — navigator.sendBeacon() e fetch() con keepalive: true — e spiega quale delle due usa realmente il tracker di Monoid, e perché, senza cookie, storage o fingerprinting.
Perché l'unload è un caso particolare
Quando un documento inizia a essere scaricato, il browser sta chiudendo il processo. Storicamente, XMLHttpRequest sincrono è stato usato in modo scorretto proprio in questo momento per bloccare la navigazione fino al completamento di una richiesta, danneggiando la reattività. I browser moderni scoraggiano attivamente questa pratica, e le richieste avviate durante unload vengono spesso annullate. La specifica Beacon esiste esattamente per risolvere questo problema: permette a una pagina di pianificare una richiesta che lo user agent garantisce di tentare, in modo asincrono, senza bloccare il caricamento della pagina successiva.
L'implicazione pratica per l'analytics: non fare mai affidamento su una normale fetch o XHR avviata da un gestore beforeunload/unload. Potrebbe semplicemente non lasciare mai la macchina.
navigator.sendBeacon
sendBeacon è stato progettato apposta per questo. Mette in coda un piccolo POST e restituisce immediatamente un booleano, che indica solo se la richiesta è stata messa in coda con successo — non se è andata a buon fine. Il browser la trasmette poi in background, anche dopo che la pagina è scomparsa.
const ok = navigator.sendBeacon('/collect', payload);
Punti di forza:
- Il trasferimento non è legato alla durata di vita del documento.
- Ha priorità bassa ed è progettato per non competere con la navigazione successiva.
- Non c'è alcuna risposta da gestire, quindi non c'è nulla da attendere.
Vincoli, secondo la specifica e le implementazioni dei browser:
- Solo POST. Non è possibile impostare metodi arbitrari.
- Controllo limitato sugli header. Il
Content-Typeviene dedotto dal tipo di payload (ad esempio, unBlobpermette di influenzarlo, ma non è possibile impostare liberamente header di richiesta personalizzati). - I payload rientrano in un limite di dati imposto dallo user agent; i beacon troppo grandi restituiscono
false.
Per un'analytics senza cookie, questo di solito basta — molti tracker adottano esattamente questa soluzione e non tornano più indietro. Monoid non lo fa, per un motivo specifico: sendBeacon non può esprimere mode: 'cors', e lo snippet di installazione di Monoid permette a un sito di puntare data-api-url verso un'origine diversa da quella della pagina host. Questo singolo vincolo decide il confronto qui sotto.
fetch con keepalive
Lo standard Fetch definisce un flag keepalive che mantiene viva una richiesta oltre la durata della pagina che l'ha avviata, offrendo la stessa durabilità di sendBeacon con un'API più ricca.
fetch('/collect', {
method: 'POST',
keepalive: true,
headers: { 'Content-Type': 'application/json' },
body: payload,
});
Punti di forza:
- Controllo completo su metodo, header e corpo della richiesta.
- Una vera
Responseispezionabile (anche se durante l'unload spesso non conviene attenderla).
Vincoli:
- La specifica Fetch limita la dimensione totale del corpo di tutte le richieste keepalive in corso a 64 KiB per documento. Superarla fa fallire la fetch. È un budget per documento, quindi più richieste keepalive concorrenti lo condividono.
- Storicamente il supporto a
keepaliveè arrivato dopo quello asendBeacon, e ci sono state delle stranezze implementative. Testate sui browser che il vostro pubblico usa realmente.
È quello che il tracker di Monoid usa per ogni richiesta — /collect, /duration ed /event allo stesso modo:
fetch(endpoint, {
method: 'POST',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' },
keepalive: true,
mode: 'cors',
});
mode: 'cors' è il fattore decisivo emerso nella sezione precedente, non la dimensione del payload o il numero di header — vale la pena ricordarlo, dato che la maggior parte degli articoli inquadra la scelta esclusivamente in base alla dimensione.
Cosa scegliere
Una regola pragmatica: usate sendBeacon per telemetria fire-and-forget in cui non serve alcuna risposta, e ricorrete a fetch con keepalive solo quando avete davvero bisogno di header personalizzati, di una richiesta strutturata più grande o del controllo sul mode cross-origin. Mantenete comunque i payload piccoli — ben al di sotto del budget di 64 KiB del keepalive — perché gli eventi di analytics devono essere minimi per progettazione. La minimizzazione dei dati non è solo una postura di compliance; è ciò che rende possibile una consegna affidabile durante l'unload. Monoid è l'eccezione che la regola già prevede: il payload è piccolo e la risposta non viene mai letta, esattamente il profilo per cui sendBeacon è stato costruito — ma il supporto a mode: 'cors' fa pendere l'ago della bilancia.
L'evento giusto da ascoltare
Non affidatevi a unload. Non è affidabile e disabilita la cache di navigazione avanti/indietro (bfcache). Le linee guida sul Page Lifecycle raccomandano di ascoltare visibilitychange e trattare la transizione verso hidden come l'ultimo momento affidabile per inviare i dati residui. Questo funziona in modo coerente sia con la chiusura su desktop, sia con il cambio scheda, sia con il passaggio in background su mobile — casi in cui unload non scatta mai.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
Il tracker di Monoid segue questo schema: un listener su visibilitychange affianca un fallback su beforeunload, così un campione di durata viene riportato sia che una scheda venga messa in background su mobile, sia che venga chiusa del tutto su desktop.
Come si inserisce in un modello senza cookie
Poiché Monoid non legge mai cookie o storage e non fa mai fingerprinting, ogni richiesta trasporta solo ciò che rivela già di per sé — un evento generico più gli header che il browser invia comunque. Non c'è alcun identificatore da far persistere, quindi non serve mai uno scambio bidirezionale per sincronizzare lo stato. Questo è ciò che rende fetch con keepalive la scelta naturale non appena mode: 'cors' esclude sendBeacon: un POST a sparo singolo la cui risposta non viene mai ispezionata non è per noi una limitazione; è ciò che allinea l'affidabilità della consegna con la privacy by design.
Comments
Loading comments…