Real-time UI: WebSocket vs SSE vs Polling
Quando usar cada tecnologia de comunicação em tempo real. Trade-offs de latência, escala e complexidade.
Definição
WebSocket: protocolo full-duplex sobre TCP — servidor e cliente enviam dados a qualquer momento. Ideal para: chat, jogos, colaboração ao vivo (Google Docs), tracking em tempo real. SSE (Server-Sent Events): canal unidirecional servidor→cliente sobre HTTP. Ideal para: feeds de notícias, dashboards de métricas, status de processamento. Polling: cliente pede periodicamente. Mais simples, mas ineficiente. Long polling melhora ao manter conexão aberta até ter dado.
Problema → Solução
Problema
Usar WebSocket para um feed de notícias read-only (overkill e mais complexo) ou polling para um app de chat (latência alta, desperdício de requests).Solução
Decision tree: bidirecional + latência crítica (< 50ms) → WebSocket. Servidor → cliente apenas, fluxo contínuo → SSE. Dados mudam raramente, simplicidade > tudo → polling. O Uber usa WebSocket para posição do motorista (bidirecional, alta frequência), SSE para atualizações de status da corrida (unidirecional).Dica de entrevista
Tip
SSE tem reconexão automática nativa (EventSource), roda sobre HTTP/2 (multiplexação), e é mais fácil de proxy/firewall do que WebSocket. Prefira SSE quando só o servidor precisa enviar dados. WebSocket puro sem biblioteca (socket.io) exige heartbeat manual para detectar conexões mortas.Código
// SSE — simples, reconexão automática
// Servidor (Next.js Route Handler)
export async function GET() {
const stream = new ReadableStream({
start(controller) {
const send = (data: object) =>
controller.enqueue(`data: ${JSON.stringify(data)}\n\n`)
// Posição do motorista a cada 2s
const interval = setInterval(async () => {
const pos = await getDriverPosition()
send({ lat: pos.lat, lng: pos.lng, ts: Date.now() })
}, 2000)
return () => clearInterval(interval) // cleanup
}
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
}
})
}
// Cliente React
function useDriverLocation(rideId: string) {
const [pos, setPos] = useState(null)
useEffect(() => {
const es = new EventSource(`/api/ride/${rideId}/position`)
es.onmessage = (e) => setPos(JSON.parse(e.data))
es.onerror = () => es.close() // EventSource reconecta sozinho
return () => es.close()
}, [rideId])
return pos
}
// WebSocket — bidirecional (chat, colaboração)
function useWebSocket(url: string) {
const ws = useRef<WebSocket | null>(null)
const [connected, setConnected] = useState(false)
useEffect(() => {
const connect = () => {
ws.current = new WebSocket(url)
ws.current.onopen = () => setConnected(true)
ws.current.onclose = () => {
setConnected(false)
setTimeout(connect, 2000) // reconectar
}
}
connect()
return () => ws.current?.close()
}, [url])
const send = useCallback((msg: object) => {
ws.current?.send(JSON.stringify(msg))
}, [])
return { connected, send }
}Perguntas de entrevista
Q.Como detectar e reconectar uma WebSocket desconectada?▾
A.Implementar heartbeat: servidor manda ping a cada 30s, cliente responde pong. Se o client não receber ping em 60s, fecha e reabre a conexão com backoff exponencial (1s, 2s, 4s, max 30s). Adicionar reconnect logic no onclose handler. socket.io faz isso por padrão — é um dos motivos de usá-lo.
Q.Como o SSE lida com reconexão?▾
A.Automaticamente via EventSource API: quando a conexão cai, o browser reconecta em ~3s por padrão e envia o header Last-Event-ID com o ID do último evento recebido. O servidor usa esse ID para retransmitir eventos perdidos. Para isso funcionar, você precisa implementar um ID incremental nos eventos e guardar histórico no servidor.
Q.WebSocket escala horizontalmente?▾
A.WebSocket é stateful — conexão fica em um servidor específico. Para escalar: usar Redis Pub/Sub ou um message broker (Kafka) como bus compartilhado. Quando evento chega em server A, ele publica no Redis; todos os servidores subscrevem e entregam para seus clientes conectados. Socket.io tem adapter de Redis pronto. Sem isso, sticky sessions são necessárias (pior).
116/122← →