Oito segundos encarando um spinner matam a conversão. Os mesmos oito segundos em chunks dão sensação de resposta imediata. Streaming de respostas de IA reduz a latência percebida de 8s para menos de 1s porque o primeiro token chega rápido e o resto flui. Server-Sent Events entrega isso com menos complexidade que WebSockets na maioria dos casos.

SSE: HTTP com eventos

SSE usa text/event-stream como content type, eventos separados por linha em branco e reconexão automática no EventSource do navegador. Cada evento cabe em poucos bytes; proxies e balanceadores lidam com o fluxo sem strain. Conexões ociosas morrem em proxy intermediário. Um comentário de keepalive (: ping a cada 15s) mantém o caminho aberto. LLM flui do servidor ao cliente; SSE cobre tudo com um request HTTP comum.

Quando WebSockets compensam

  • Fluxo bidirecional real: agentes de voz, sessões colaborativas, interrupção do usuário no meio da fala
  • Latência de mensagem abaixo de 100ms muda a experiência percebida
  • Multiplexação de várias streams lógicas numa conexão única

Streaming no backend

O servidor encaminha os deltas do provedor conforme chega e descarrega buffers sem esperar acumular. O nginx segura chunks por padrão e queima a experiência: proxy_buffering off; na location ou X-Accel-Buffering: no na resposta resolvem. Compressão gzip em respostas parciais também retém bytes; desative nesse endpoint.

Cancelamento

O usuário clicou em stop? O cliente aborta o fetch, o servidor detecta a conexão fechada (request.on('close') no Express, cancelamento de generator no FastAPI) e encerra a chamada upstream. Provedores cobram pelos tokens gerados até o corte; o corte precisa acontecer dos dois lados. Registre o cancelamento nos logs com o ID da sessão. Sem essa cadeia, toda sessão abandonada paga tokens que ninguém leu.

Erros no meio do stream

O provedor derruba a conexão após metade da resposta. Envie o que chegou mais um evento final de erro estruturado; o cliente renderiza o parcial com marcador de falha e botão de regenerar. Quando o provedor suporta continuação, regenere a partir do último chunk recebido. Silêncio até o timeout é a alternativa ruim.

Notas por framework

  • Next.js: route handlers streameam ReadableStream sem biblioteca extra
  • Express: headers Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, com res.flush() após cada chunk
  • FastAPI: StreamingResponse de fábrica consumindo generators async