A maioria dos dados de análise, telemetria e Core Web Vitals tem prioridade menor do que responder ao usuário. Portanto, é possível adiar o envio até que o usuário saia da página. A exceção é quando você precisa fornecer atualizações em tempo real. Nesse caso, a pontualidade é importante.
A melhor maneira de fornecer atualizações de análise em tempo real, minimizando os beacons, é usar fetchLater() com a opção de configuração activateAfter. Isso permite que você elimine o debounce e agrupe todos os eventos de análise que ocorrem em um determinado período em um único beacon, que é enviado de forma confiável mesmo que o usuário saia da página antes do tempo limite.
Como fazer a implementação
Programar a solicitação: assim que houver dados de análise relevantes disponíveis, chame
fetchLater()com o payload de dados e transmita um valoractivateAfter. Isso coloca os dados em fila para serem enviados após esse período ou se o usuário sair da página antes.Agrupar vários eventos em lote: se um novo evento de análise ocorrer antes que o tempo limite de
activateAfterexpire, cancele a solicitação programada anteriormente e chamefetchLater()novamente (com o mesmo valor deactivateAfter) com a fila de eventos completa em um único payload.Redefinir a fila de eventos quando o tempo limite expirar: se um novo evento de análise ocorrer depois que o beacon programado for enviado (ou seja, o valor
activateddo resultadofetchLater()fortrue), redefina a fila de eventos.Deixe o navegador cuidar do resto: se o usuário sair ou fechar a guia antes que o tempo limite de
activateAfterexpire, o navegador ainda vai enviar de forma confiável o payload da sua chamadafetchLater()mais recente.
Exemplo de código
Esse código rastreia todos os eventos load e click em uma página e agrupa todos os eventos que ocorrem em um tempo limite de 10 segundos.
// 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)});
});
Práticas recomendadas
- USE
fetchLater()com a opçãoactivateAfterdefinida para agrupar vários eventos de análise que ocorrem próximos uns dos outros. - USE um
AbortControllerpara cancelar a busca pendente quando um novo evento de análise ocorrer. - VERIFIQUE se você não está agrupando muitos eventos em lote e excedendo a cota de
fetchLater()(atualmente, cerca de 64 KB por origem). - FAÇA o agrupamento de chamadas para
fetchLater()em umtry/catchpara lidar com erros de cota. - FAÇA a detecção de recursos da presença de
fetchLater()emglobalThise implemente uma estratégia de substituição para navegadores que não oferecem suporte à API. - NÃO use um objeto
ReadableStreampara o corpo da solicitação, porque isso vai causar um erro.
Suporte ao navegador e estratégias de substituição
Portanto, uma estratégia de fallback geralmente é necessária.
No entanto, considerando os benefícios de confiabilidade e desempenho dessa API, use fetchLater() se o navegador for compatível.
Polyfill fetchLater()
Use o seguinte polyfill mínimo de fetchLater(), que implementa a API da forma mais próxima possível em navegadores sem suporte.
A única diferença de comportamento notável com esse polyfill é que, em vez de enviar o payload quando o usuário sai da página, ele o envia sempre que o visibilityState da página muda para "oculto", já que esse é o sinal de fim de sessão mais confiável e amplamente disponível atualmente.
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;
},
};
};