Ocho segundos mirando un spinner matan la conversión. Los mismos ocho segundos en chunks dan sensación de respuesta inmediata. El streaming de respuestas de IA reduce la latencia percibida de 8s a menos de 1s porque el primer token llega rápido y el resto fluye. Server-Sent Events entrega eso con menos complejidad que WebSockets en la mayoría de los casos.

SSE: HTTP con eventos

SSE usa text/event-stream como content type, eventos separados por línea en blanco y reconexión automática en el EventSource del navegador. Cada evento cabe en pocos bytes; proxies y balanceadores manejan el flujo sin strain. Las conexiones ociosas mueren en un proxy intermedio. Un comentario de keepalive (: ping cada 15s) mantiene el camino abierto. El output del LLM fluye del servidor al cliente; SSE cubre todo con un request HTTP común.

Cuándo convienen los WebSockets

  • Flujo bidireccional real: agentes de voz, sesiones colaborativas, interrupción del usuario en medio del habla
  • Latencia de mensaje por debajo de 100ms cambia la experiencia percibida
  • Multiplexación de varias streams lógicas en una conexión única

Streaming en el backend

El servidor reenvía los deltas del proveedor conforme llegan y descarga buffers sin esperar a acumular. nginx retiene chunks por defecto y arruina la experiencia: proxy_buffering off; en la location o X-Accel-Buffering: no en la respuesta lo resuelven. La compresión gzip en respuestas parciales también retiene bytes; desactívala en ese endpoint.

Cancelación

¿El usuario hizo clic en stop? El cliente aborta el fetch, el servidor detecta la conexión cerrada (request.on('close') en Express, cancelación de generator en FastAPI) y termina la llamada upstream. Los proveedores cobran por los tokens generados hasta el corte; el corte necesita suceder de ambos lados. Registra la cancelación en los logs con el ID de sesión. Sin esa cadena, toda sesión abandonada paga tokens que nadie leyó.

Errores en medio del stream

El proveedor tumba la conexión tras la mitad de la respuesta. Envía lo que llegó más un evento final de error estructurado; el cliente renderiza el parcial con marcador de fallo y botón de regenerar. Cuando el proveedor soporta continuación, regenera desde el último chunk recibido. Silencio hasta el timeout es la alternativa mala.

Notas por framework

  • Next.js: los route handlers streamean ReadableStream sin librería extra
  • Express: headers Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, con res.flush() después de cada chunk
  • FastAPI: StreamingResponse de fábrica consumiendo generators async