navigator.sendBeacon vs fetch keepalive: Entrega Confiável de Analytics Sem Bloquear a Navegação
Como enviar payloads de analytics que sobrevivem ao descarregamento da página sem travar a navegação — e por que a escolha entre sendBeacon e fetch keepalive importa para coleta sem cookies.
Coletar uma visualização de página ou um evento é fácil enquanto a página está viva. A parte difícil é entregar esse último payload no momento em que o usuário navega para outro lugar, fecha a aba ou coloca o app em segundo plano no celular. Errar isso significa perder dados ou, pior, atrasar a própria navegação que o usuário pediu. Este post compara as duas opções viáveis — navigator.sendBeacon() e fetch() com keepalive: true — e explica qual delas o tracker do Monoid realmente usa, e por quê, sem cookies, armazenamento ou fingerprinting.
Por que o descarregamento é um caso especial
Quando um documento começa a ser descarregado, o navegador está se desligando. XMLHttpRequest síncrono foi historicamente abusado aqui para bloquear a navegação até uma requisição terminar, o que prejudica a responsividade. Navegadores modernos desencorajam ativamente isso, e requisições iniciadas durante o unload são frequentemente canceladas. A especificação Beacon existe justamente para resolver isso: ela permite que uma página agende uma requisição que o user agent garante tentar, de forma assíncrona, sem bloquear o carregamento da próxima página.
A implicação prática para analytics: nunca confie em um fetch ou XHR normal disparado a partir de um handler de beforeunload/unload. Ele pode simplesmente nunca sair da máquina.
navigator.sendBeacon
sendBeacon foi feito exatamente para isso. Ele enfileira um pequeno POST e retorna um booleano imediatamente, dizendo apenas se a requisição foi enfileirada com sucesso — não se ela teve êxito. O navegador então a transmite em segundo plano, mesmo depois que a página já se foi.
const ok = navigator.sendBeacon('/collect', payload);
Pontos fortes:
- A transferência não está atrelada ao tempo de vida do documento.
- É de baixa prioridade e projetada para não competir com a próxima navegação.
- Não há resposta para tratar, então não há nada para aguardar.
Restrições, segundo a especificação e as implementações dos navegadores:
- Apenas POST. Você não pode definir métodos arbitrários.
- Controle limitado sobre cabeçalhos. O
Content-Typeé inferido a partir do tipo do payload (por exemplo, umBlobpermite influenciá-lo, mas você não pode definir livremente cabeçalhos de requisição personalizados). - Payloads contam contra um limite de dados do user agent; beacons grandes demais retornam
false.
Para analytics sem cookies, isso costuma ser suficiente — vários trackers usam exatamente isso e nunca olham para trás. O Monoid não usa, por um motivo específico: sendBeacon não consegue expressar mode: 'cors', e o snippet de instalação do Monoid permite que um site aponte data-api-url para uma origem diferente da página host. Essa única restrição decide a comparação abaixo.
fetch com keepalive
O padrão Fetch define uma flag keepalive que mantém uma requisição viva além da página que a iniciou, dando a você a durabilidade do sendBeacon com uma API mais rica.
fetch('/collect', {
method: 'POST',
keepalive: true,
headers: { 'Content-Type': 'application/json' },
body: payload,
});
Pontos fortes:
- Controle total sobre método, cabeçalhos e corpo.
- Uma
Responsereal que você pode inspecionar (embora durante o descarregamento, muitas vezes você não deva aguardá-la).
Restrições:
- A especificação Fetch limita o tamanho total do corpo de todas as requisições keepalive em andamento a 64 KiB por documento. Ultrapasse isso e o fetch é rejeitado. É um orçamento por documento, então várias requisições keepalive concorrentes o compartilham.
- Historicamente, o suporte a
keepaliveficou atrás dosendBeacon, e houve peculiaridades de implementação. Teste nos navegadores que seu público realmente usa.
É isso que o tracker do Monoid usa para toda requisição — /collect, /duration e /event igualmente:
fetch(endpoint, {
method: 'POST',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' },
keepalive: true,
mode: 'cors',
});
O mode: 'cors' é o fator decisivo da seção anterior, não o tamanho do payload nem a quantidade de cabeçalhos — vale lembrar, já que a maioria dos textos sobre o tema enquadra a escolha puramente em torno do tamanho.
Qual escolher
Uma regra pragmática: use sendBeacon para telemetria de disparar-e-esquecer, onde você não precisa de nada de volta, e recorra ao fetch com keepalive apenas quando você genuinamente precisa de cabeçalhos personalizados, uma requisição estruturada maior, ou controle sobre o mode entre origens. Mantenha os payloads pequenos de qualquer forma — bem abaixo do orçamento de 64 KiB do keepalive — porque eventos de analytics devem ser mínimos por design. Minimização de dados não é apenas uma postura de conformidade; é o que torna possível uma entrega confiável no descarregamento. O Monoid é a exceção que a regra já prevê: o payload é pequeno e a resposta nunca é lida, exatamente o perfil para o qual o sendBeacon foi criado — mas o suporte a mode: 'cors' prevalece.
O evento certo para escutar
Não use o unload. Ele não é confiável e quebra o cache de avanço/retrocesso (bfcache). A orientação do Page Lifecycle recomenda escutar visibilitychange e tratar a transição para hidden como seu último momento confiável para descarregar dados. Isso funciona de forma consistente entre o fechamento no desktop, a troca de aba e o envio para segundo plano no celular — casos em que o unload nunca dispara.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
O tracker do Monoid segue esse padrão: um listener de visibilitychange fica ao lado de um fallback de beforeunload, então uma amostra de duração é reportada seja quando uma aba é enviada para segundo plano no celular, seja quando é fechada de vez no desktop.
Como isso se encaixa em um modelo sem cookies
Como o Monoid nunca lê cookies ou armazenamento e nunca faz fingerprinting, cada requisição carrega apenas o que já revela por si só — um evento genérico mais os cabeçalhos que o navegador envia de qualquer forma. Não há identificador para persistir, então nunca precisamos de uma troca bidirecional para sincronizar estado. É isso que torna o fetch com keepalive a escolha fácil assim que o mode: 'cors' tira o sendBeacon da mesa: um POST de disparo único cuja resposta nunca inspecionamos não é uma limitação para nós; é o que alinha a confiabilidade da entrega com privacidade desde a concepção.
Comments
Loading comments…