Sebagian besar data analisis, telemetri, dan analisis Core Web Vitals memiliki prioritas yang lebih rendah daripada merespons pengguna, sehingga Anda dapat menunda pengirimannya dengan aman hingga pengguna keluar dari halaman. Pengecualian untuk hal ini adalah jika Anda perlu memberikan pembaruan real-time, yang berarti ketepatan waktu sangat penting.
Cara optimal untuk memberikan update analisis real-time, sekaligus meminimalkan beacon, adalah menggunakan fetchLater() dengan opsi konfigurasi activateAfter. Hal ini memungkinkan Anda melakukan debounce dan pengelompokan secara efektif untuk semua peristiwa analisis yang terjadi dalam jangka waktu tertentu ke dalam satu beacon, yang dikirim dengan andal meskipun pengguna keluar dari halaman sebelum waktu tunggu habis.
Cara menerapkan
Jadwalkan permintaan: Segera setelah data analisis yang relevan tersedia, panggil
fetchLater()dengan payload data Anda dan teruskan nilaiactivateAfter. Tindakan ini akan mengantrekan data untuk dikirim setelah jangka waktu tersebut berlalu, atau jika pengguna meninggalkan halaman sebelum jangka waktu tersebut berlalu.Mengelompokkan beberapa peristiwa: Jika peristiwa analisis baru terjadi sebelum waktu tunggu
activateAfterberakhir, batalkan permintaan yang dijadwalkan sebelumnya dan panggilfetchLater()lagi (dengan nilaiactivateAfteryang sama) dengan antrean peristiwa lengkap dalam satu payload.Reset antrean peristiwa saat waktu tunggu berakhir: Jika peristiwa analisis baru terjadi setelah beacon terjadwal berhasil dikirim (yaitu nilai
activatedhasilfetchLater()adalahtrue), reset antrean peristiwa.Biarkan browser menangani sisanya: Jika pengguna keluar atau menutup tab sebelum waktu tunggu
activateAfterberakhir, browser akan tetap mengirimkan payload dari panggilanfetchLater()terbaru Anda dengan andal.
Kode contoh
Kode ini melacak semua peristiwa load dan click di halaman, serta mengelompokkan semua peristiwa yang terjadi dalam waktu tunggu 10 detik.
// 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)});
});
Praktik Terbaik
- LAKUKAN penggunaan
fetchLater()dengan opsiactivateAfteryang ditetapkan untuk mengelompokkan beberapa peristiwa analisis yang terjadi dalam jarak yang berdekatan. - LAKUKAN penggunaan
AbortControlleruntuk membatalkan pengambilan yang tertunda jika terjadi peristiwa analisis baru. - LAKUKAN pemeriksaan untuk memastikan Anda tidak mengelompokkan terlalu banyak peristiwa sekaligus dan melebihi kuota
fetchLater()(saat ini sekitar 64 KB per origin). - LAKUKAN penggabungan panggilan ke
fetchLater()dalamtry/catchuntuk menangani error kuota. - LAKUKAN deteksi fitur keberadaan
fetchLater()diglobalThisdan terapkan strategi penggantian untuk browser yang tidak mendukung API. - JANGAN gunakan objek
ReadableStreamuntuk isi permintaan, karena akan menyebabkan error.
Dukungan browser dan strategi penggantian
Oleh karena itu, strategi penggantian biasanya diperlukan.
Namun, mengingat manfaat peningkatan keandalan dan performa API ini, fetchLater() harus digunakan jika browser mendukungnya.
fetchLater() polyfill
Gunakan polyfill fetchLater() minimal berikut, yang mengimplementasikan API semirip mungkin di browser yang tidak mendukung.
Satu-satunya perbedaan perilaku yang signifikan dengan polyfill ini adalah, alih-alih mengirim payload saat pengguna meninggalkan halaman, polyfill ini mengirimkannya setiap kali visibilityState halaman berubah menjadi "tersembunyi", karena ini adalah sinyal akhir sesi yang paling andal dan tersedia secara luas saat ini.
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;
},
};
};