“Precisa atualizar sem recarregar a página” parece uma frase curta. O problema é que ela costuma empurrar a arquitetura direto para WebSocket, como se qualquer tela viva precisasse de um canal bidirecional aberto. Na prática, muita coisa chamada de tempo real tolera alguns segundos de atraso, recebe dados em uma direção só ou nem fica aberta tempo suficiente para justificar uma conexão persistente.
Polling, Server-Sent Events (SSE) e WebSocket não são três degraus da mesma escada. São respostas para fluxos diferentes. A escolha começa por quatro perguntas: quem inicia a comunicação, com que frequência os dados mudam, quanto atraso o produto aceita e o que a equipe consegue operar quando a conexão cai.
Polling não é uma vergonha arquitetural
No polling, o navegador pergunta periodicamente se há novidade. É HTTP comum, passa por infraestrutura conhecida, funciona bem com autenticação, observabilidade e cache, e não mantém uma conexão aberta por usuário. Para um painel atualizado a cada 30 segundos, o status de uma exportação ou uma caixa de notificações, isso pode ser exatamente o necessário.
async function atualizarStatus() {
try {
const resposta = await fetch('/api/exportacoes/42');
const status = await resposta.json();
renderizar(status);
} finally {
setTimeout(atualizarStatus, 5000);
}
}
atualizarStatus();
Usar setTimeout depois que a requisição termina evita empilhar chamadas quando o servidor demora mais que o intervalo. Também vale pausar ou reduzir a frequência com a aba em segundo plano, aumentar o intervalo após erros e responder com ETag ou Last-Modified quando isso fizer sentido.
O custo aparece quando a frequência é alta e quase todas as respostas dizem “nada mudou”. Mil clientes consultando a cada segundo viram mil requisições por segundo mesmo num dia parado. Se a exigência é entregar eventos assim que surgem, o modelo começa a gastar trabalho só para descobrir o silêncio.
SSE é uma ótima resposta para servidor → navegador
Server-Sent Events mantém uma resposta HTTP aberta no formato text/event-stream. O servidor envia eventos conforme eles aparecem e o cliente usa a API EventSource. O canal é de mão única: servidor para navegador. O cliente continua podendo enviar comandos com fetch.
const eventos = new EventSource('/api/builds/stream');
eventos.addEventListener('status', (event) => {
const build = JSON.parse(event.data);
atualizarBuild(build);
});
eventos.onerror = () => {
mostrarAviso('Tentando reconectar…');
};
A API reconecta automaticamente quando a conexão é interrompida. O protocolo também prevê os campos id e retry; com um identificador por evento, o navegador pode enviar Last-Event-ID ao reconectar. Isso ajuda a retomar o fluxo sem perder o que aconteceu no intervalo, desde que o servidor mantenha um histórico ou cursor.
SSE combina com progresso de jobs, logs ao vivo, cotações, feeds e notificações. Ainda há trabalho operacional: proxies precisam aceitar respostas longas, buffers podem atrasar eventos, keep-alives evitam conexões consideradas ociosas e a autenticação precisa respeitar as limitações da interface EventSource. Em HTTP/1, navegadores também impõem um limite baixo de conexões por origem; HTTP/2 negocia vários streams e alivia esse ponto.
WebSocket entra quando os dois lados realmente conversam
WebSocket abre um canal full-duplex: navegador e servidor podem mandar mensagens a qualquer momento. É o encaixe natural para chat, edição colaborativa, jogos, presença online e experiências em que comandos e eventos circulam continuamente nas duas direções.
A flexibilidade cobra seu preço. A aplicação define formato das mensagens, correlação entre pedido e resposta, autorização por ação, heartbeat, retomada de sessão e estratégia de reconexão. Load balancers e gateways precisam aceitar o upgrade da conexão. Escalar horizontalmente costuma exigir uma camada de pub/sub ou algum modo de localizar a sessão certa.
A API clássica de WebSocket no navegador também não oferece backpressure. Se mensagens chegam mais rápido do que a aplicação processa, memória e CPU podem sofrer. E uma conexão aberta deve ser tratada no ciclo de vida da página: a documentação da MDN recomenda fechar no evento pagehide, inclusive para não atrapalhar o back/forward cache. Em produção, use wss://.
Uma regra prática que cabe na reunião
- Escolha polling quando segundos ou minutos de atraso são aceitáveis, a mudança é pouco frequente e simplicidade pesa mais.
- Escolha SSE quando o servidor precisa empurrar uma sequência de atualizações para o navegador, mas os comandos do cliente cabem em HTTP normal.
- Escolha WebSocket quando há conversa frequente nos dois sentidos e a latência faz parte da experiência.
O teste mais honesto é desenhar as setas. Se quase todas apontam do servidor para o cliente, WebSocket talvez seja complexidade antecipada. Se o produto aceita ver a mudança na próxima consulta, SSE talvez também seja demais. E, qualquer que seja a tecnologia, “tempo real” só fica confiável quando há uma resposta para reconexão, duplicidade, ordenação, autenticação e observabilidade.
Escolha também pela recuperação
- Polling: retoma naturalmente na próxima consulta; use ETag, cursor ou versão para não transferir tudo.
- SSE: envie IDs crescentes e aceite
Last-Event-IDpara reproduzir a lacuna. - WebSocket: defina protocolo de autenticação, heartbeat, resume token e ressincronização completa.
“Reconectou” não significa “está consistente”. Durante a queda, eventos podem ter sido perdidos, compactados ou entregues fora de ordem. O cliente precisa informar a última versão aplicada; o servidor responde com replay quando ainda possui o histórico ou com um snapshot quando a lacuna é grande demais.
Backpressure separa tempo real de fila infinita
Um cliente lento não deve acumular memória sem limite no servidor. Combine tamanho máximo da fila, compactação de estados substituíveis e desconexão com motivo explícito. Presença pode descartar estados intermediários; uma transação financeira não.
Reconexões simultâneas também precisam de backoff e jitter. Depois de uma queda regional, milhares de sockets tentando voltar no mesmo segundo podem prolongar o incidente.
Em aplicações local-first, o transporte é só uma peça: op-log, conflitos e tombstones definem se a sincronização converge. WebSocket reduz latência, mas não escolhe qual edição vence.

