De meeste analyse-, telemetrie- en Core Web Vitals-analysegegevens hebben een lagere prioriteit dan reageren op de gebruiker. Je kunt het versturen ervan dus veilig uitstellen totdat de gebruiker de pagina verlaat. De uitzondering hierop is als je realtime updates moet leveren. In dat geval is tijdigheid wel belangrijk.
De optimale manier om realtime analytische updates te bieden en tegelijkertijd het aantal beacons te minimaliseren, is door fetchLater() te gebruiken met de configuratieoptie activateAfter. Zo kunt u alle Analytics-gebeurtenissen die binnen een bepaalde periode plaatsvinden, effectief debouncen en batchen in één beacon die betrouwbaar wordt verstuurd, zelfs als de gebruiker de pagina verlaat voordat de time-out verloopt.
Hoe wordt dit geïmplementeerd
Plan het verzoek: Zodra er relevante analysegegevens beschikbaar zijn, roep je
fetchLater()aan met je gegevenspayload en geef je eenactivateAfter-waarde door. De gegevens worden dan in de wachtrij geplaatst om te worden verstuurd nadat die tijd is verstreken of als de gebruiker de pagina eerder verlaat.Meerdere gebeurtenissen samenvoegen: Als er een nieuwe analysegebeurtenis plaatsvindt voordat de time-out van
activateAfteris verstreken, annuleert u het eerder geplande verzoek en roept ufetchLater()opnieuw aan (met dezelfde waarde vooractivateAfter) met de volledige gebeurteniswachtrij in één payload.De gebeurteniswachtrij resetten als de time-out verloopt: Als er een nieuwe analysegebeurtenis plaatsvindt nadat de geplande beacon is verstuurd (dat wil zeggen dat de waarde van het resultaat
fetchLater()activatedistrue), reset je de gebeurteniswachtrij.Laat de browser de rest afhandelen: Als de gebruiker weggaat of het tabblad sluit voordat de time-out van
activateAfteris verstreken, stuurt de browser de payload van uw meest recentefetchLater()-aanroep nog steeds betrouwbaar.
Voorbeeldcode
Deze code houdt alle load- en click-gebeurtenissen op een pagina bij en groepeert alle gebeurtenissen die binnen een time-out van 10 seconden plaatsvinden.
// Replace with your analytics endpoint.
const ANALYTICS_ENDPOINT = '/path/to/analytics/endpoint';
// Replace with a time window of your choice. All analytics events that
// occur within this time window will be batched together.
const BATCH_WINDOW = 10 * 1000;
// The maximum number of events to batch. Pick a number that is unlikely
// to overflow the fetchLater() quota for the page.
const MAX_QUEUE_SIZE = 100;
const eventQueue = [];
let fetchLaterResult;
let fetchLaterController;
function trackEvent(eventData) {
// If the previously queued beacon has already been sent, or if the
// max queue size has been met, reset the queue.
if (fetchLaterResult?.activated || eventQueue.length > MAX_QUEUE_SIZE) {
fetchLaterController = null;
fetchLaterResult = null;
eventQueue.length = 0;
}
eventQueue.push(eventData);
// Abort any pending beacons before creating a new one.
if (fetchLaterController) {
fetchLaterController.abort();
}
fetchLaterController = new AbortController();
// Schedule a fetch for the events to be sent when the batch window expires.
// IMPORTANT: wrap the call in a try/catch to handle quota errors.
try {
fetchLaterResult = fetchLater(ANALYTICS_ENDPOINT, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify(eventQueue),
signal: fetchLaterController.signal,
activateAfter: BATCH_WINDOW,
});
} catch (error) {
// Handle errors as needed.
}
}
// Track page loads.
window.addEventListener('load', () => {
trackEvent({type: 'page_load'});
});
// Track click events.
window.addEventListener('click', (event) => {
trackEvent({type: 'click', target: serializeElement(event.target)});
});
Praktische tips
- Gebruik WEL
fetchLater()met de optieactivateAfterom meerdere analysegebeurtenissen die vlak na elkaar plaatsvinden, te bundelen. - Gebruik WEL een
AbortControllerom de in behandeling zijnde ophaalactie te annuleren als er een nieuwe analysegebeurtenis plaatsvindt. - WEL: Controleer of u niet te veel gebeurtenissen bundelt en het
fetchLater()-quotum overschrijdt (op dit moment ongeveer 64 KB per oorsprong). - WEL: Verpak aanroepen naar
fetchLater()in eentry/catchom quotumfouten af te handelen. - WEL de aanwezigheid van
fetchLater()opglobalThisdetecteren en een fallback-strategie implementeren voor browsers die de API niet ondersteunen. - Gebruik GEEN
ReadableStream-object voor de verzoektekst, omdat dit een fout veroorzaakt.
Browserondersteuning en fallback-strategieën
Daarom is meestal een reservestrategie vereist.
Vanwege de verbeterde betrouwbaarheid en prestatievoordelen van deze API moet fetchLater() worden gebruikt als de browser dit ondersteunt.
fetchLater() polyfill
Gebruik de volgende minimale fetchLater() polyfill, die de API zo nauwkeurig mogelijk implementeert in niet-ondersteunde browsers.
Het enige opmerkelijke verschil in gedrag met deze polyfill is dat de payload niet wordt verstuurd wanneer de gebruiker de pagina verlaat, maar wanneer de visibilityState van de pagina verandert in 'hidden'. Dit is namelijk het meest betrouwbare signaal voor het einde van de sessie dat op dit moment breed beschikbaar is.
globalThis.fetchLater ??= function fetchLater(url, init = {}) {
let timeoutHandle;
let activated = false;
function sendNow() {
if (!(init.signal && init.signal.aborted)) {
// Use fetch keepalive if the browser supports it or if custom fetch
// parameters are specified (e.g. custom headers or methods).
// Otherwise fall back to `navigator.sendBeacon()`.
if (
'keepalive' in Request.prototype ||
init.method !== 'POST' ||
init.headers
) {
fetch(url, Object.assign({}, init, {keepalive: true}));
activated = true;
} else {
activated = navigator.sendBeacon(url, init.body);
}
}
destroy();
}
function destroy() {
document.removeEventListener('visibilitychange', sendNow);
clearTimeout(timeoutHandle);
}
if (document.visibilityState === 'hidden') {
// If the beacon was created while the page is already hidden, send data
// ASAP but wait until the next microtask to allow all sync code to run.
queueMicrotask(sendNow);
} else {
document.addEventListener('visibilitychange', sendNow);
if (typeof init.activateAfter === 'number' && init.activateAfter >= 0) {
timeoutHandle = setTimeout(sendNow, init.activateAfter);
}
}
if (init.signal) {
init.signal.addEventListener('abort', destroy);
}
return {
get activated() {
return activated;
},
};
};