Większość danych analitycznych, telemetrycznych i dotyczących Core Web Vitals ma niższy priorytet niż odpowiadanie użytkownikowi, więc możesz bezpiecznie odłożyć ich wysyłanie do momentu, gdy użytkownik opuści stronę. Wyjątkiem jest sytuacja, gdy musisz dostarczać aktualizacje w czasie rzeczywistym – wtedy liczy się aktualność.
Optymalnym sposobem na dostarczanie aktualizacji statystyk w czasie rzeczywistym przy jednoczesnym ograniczeniu liczby sygnałów jest użycie fetchLater() z opcją konfiguracji activateAfter. Dzięki temu możesz skutecznie eliminować powtórzenia i grupować wszystkie zdarzenia analityczne, które wystąpiły w danym przedziale czasu, w jedną sygnalizację, która jest niezawodnie wysyłana, nawet jeśli użytkownik opuści stronę przed upływem limitu czasu.
Jak to zrobić?
Zaplanuj żądanie: gdy tylko będą dostępne odpowiednie dane analityczne, wywołaj
fetchLater()z ładunkiem danych i przekaż wartośćactivateAfter. Spowoduje to umieszczenie danych w kolejce do wysłania po upływie tego czasu lub po wcześniejszym opuszczeniu strony przez użytkownika.Grupowanie wielu zdarzeń: jeśli nowe zdarzenie analityczne wystąpi przed upływem limitu czasu
activateAfter, anuluj wcześniej zaplanowane żądanie i ponownie wywołajfetchLater()(z tą samą wartościąactivateAfter) z pełną kolejką zdarzeń w jednym ładunku.Resetowanie kolejki zdarzeń po upływie limitu czasu: jeśli po pomyślnym wysłaniu zaplanowanego sygnału (czyli gdy wartość
activatedwynikufetchLater()totrue) wystąpi nowe zdarzenie analityczne, zresetuj kolejkę zdarzeń.Pozwól przeglądarce zająć się resztą: jeśli użytkownik opuści stronę lub zamknie kartę przed upływem limitu czasu
activateAfter, przeglądarka i tak niezawodnie wyśle ładunek z ostatniego wywołaniafetchLater().
Przykładowy kod
Ten kod śledzi wszystkie zdarzenia load i click na stronie i grupuje wszystkie zdarzenia, które wystąpiły w ciągu 10 sekund.
// 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)});
});
Sprawdzone metody
- UŻYWAJ
fetchLater()z opcjąactivateAfter, aby grupować wiele zdarzeń analitycznych, które występują blisko siebie. - UŻYWAJ
AbortController, aby anulować oczekujące pobieranie w przypadku wystąpienia nowego zdarzenia analitycznego. - DO check to ensure you do not batch too many events together and exceed the
fetchLater()quota (currently roughly 64KB per origin). - ZALECENIE: wywołania
fetchLater()umieszczaj wtry/catch, aby obsługiwać błędy związane z limitami. - DO feature detect the presence of
fetchLater()onglobalThisand implement a fallback strategy for browsers that don't support the API. - NIE używaj obiektu
ReadableStreamw treści żądania, ponieważ spowoduje to błąd.
Obsługa przeglądarek i strategie zastępcze
Dlatego zwykle wymagana jest strategia zastępcza.
Jednak ze względu na większą niezawodność i wydajność tego interfejsu API należy używać fetchLater(), jeśli przeglądarka go obsługuje.
fetchLater() polyfill
Użyj tego minimalnego kodu polyfill fetchLater(), który implementuje interfejs API w jak największym stopniu w nieobsługiwanych przeglądarkach.
Jedyna istotna różnica w działaniu tego kodu polyfill polega na tym, że zamiast wysyłać ładunek, gdy użytkownik opuszcza stronę, wysyła go, gdy visibilityState strony zmienia się na „ukryty”, ponieważ jest to najbardziej niezawodny sygnał zakończenia sesji, który jest obecnie powszechnie dostępny.
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;
},
};
};