Die meisten Analyse-, Telemetrie- und Core Web Vitals-Analysedaten haben eine niedrigere Priorität als die Antwort an den Nutzer. Sie können das Senden dieser Daten also bedenkenlos aufschieben, bis der Nutzer die Seite verlässt. Eine Ausnahme besteht, wenn Sie Echtzeit-Updates bereitstellen müssen. In diesem Fall ist die Aktualität wichtig.
Die optimale Methode, um Echtzeit-Analyseupdates bereitzustellen und gleichzeitig Beacons zu minimieren, ist die Verwendung von fetchLater() mit der Konfigurationsoption activateAfter. So können Sie alle Analyseereignisse, die innerhalb eines bestimmten Zeitfensters auftreten, effektiv entprellen und in einem einzigen Beacon zusammenfassen, der zuverlässig gesendet wird, auch wenn der Nutzer die Seite vor Ablauf des Zeitlimits verlässt.
Implementierung
Anfrage planen: Sobald relevante Analysedaten verfügbar sind, rufen Sie
fetchLater()mit Ihrer Datennutzlast auf und übergeben Sie einenactivateAfter-Wert. Die Daten werden dann in die Warteschlange gestellt und nach Ablauf dieser Zeit oder wenn der Nutzer die Seite vorher verlässt, gesendet.Mehrere Ereignisse zusammenfassen: Wenn ein neues Analyseereignis auftritt, bevor das Zeitlimit
activateAfterabläuft, brechen Sie die zuvor geplante Anfrage ab und rufen SiefetchLater()noch einmal (mit demselbenactivateAfter-Wert) mit der vollständigen Ereigniswarteschlange in einer einzelnen Nutzlast auf.Ereigniswarteschlange nach Ablauf des Zeitlimits zurücksetzen: Wenn ein neues Analytics-Ereignis auftritt, nachdem der geplante Beacon erfolgreich gesendet wurde (d.h. der
activated-Wert desfetchLater()-Ergebnisses isttrue), wird die Ereigniswarteschlange zurückgesetzt.Den Browser den Rest erledigen lassen: Wenn der Nutzer die Seite verlässt oder den Tab schließt, bevor das Zeitlimit von
activateAfterabläuft, sendet der Browser die Nutzlast aus dem letztenfetchLater()-Aufruf trotzdem zuverlässig.
Beispielcode
Mit diesem Code werden alle load- und click-Ereignisse auf einer Seite erfasst und alle Ereignisse, die innerhalb eines Zeitlimits von 10 Sekunden auftreten, zusammengefasst.
// 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)});
});
Best Practices
- DO: Verwenden Sie
fetchLater()mit der OptionactivateAfter, um mehrere Analytics-Ereignisse, die kurz nacheinander auftreten, in einem Batch zusammenzufassen. - EMPFOHLEN: Verwenden Sie einen
AbortController, um den ausstehenden Abruf abzubrechen, wenn ein neues Analytics-Ereignis auftritt. - DO check to ensure you do not batch too many events together and exceed the
fetchLater()quota (currently roughly 64KB per origin). - EMPFOHLEN: Schließen Sie Aufrufe von
fetchLater()in einentry/catchein, um Kontingentfehler zu beheben. - DO feature detect the presence of
fetchLater()onglobalThisand implement a fallback strategy for browsers that don't support the API. - Verwenden Sie KEIN
ReadableStream-Objekt für den Anfragetext, da dies zu einem Fehler führt.
Browserunterstützung und Fallback-Strategien
Daher ist in der Regel eine Fallback-Strategie erforderlich.
Aufgrund der verbesserten Zuverlässigkeit und Leistung dieser API sollte jedoch fetchLater() verwendet werden, wenn der Browser dies unterstützt.
fetchLater()-Polyfill
Verwenden Sie das folgende minimale fetchLater()-Polyfill, das die API in nicht unterstützten Browsern so genau wie möglich implementiert.
Der einzige nennenswerte Verhaltensunterschied bei diesem Polyfill besteht darin, dass die Nutzlast nicht gesendet wird, wenn der Nutzer die Seite verlässt, sondern wenn sich der visibilityState-Wert der Seite in „hidden“ ändert. Dies ist das zuverlässigste Signal für das Ende einer Sitzung, das derzeit allgemein verfügbar ist.
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;
},
};
};