Why architecture matters here

SSE architecture matters because it's the simplest browser streaming. WebSocket needs upgrade + framing; WebTransport is newer + limited. SSE is HTTP; works everywhere; browser handles reconnect for you.

Cost is one connection per client. HTTP/2 multiplexing lets many SSE streams share a TCP connection.

Reliability from automatic reconnect + Last-Event-ID. Server can implement resume.

Advertisement

The architecture: every piece explained

Walk the diagram top to bottom.

Browser EventSource. `new EventSource(url)`; native API.

Server. Responds with Content-Type: text/event-stream and keeps connection open.

Streamed events. `data: {payload}\n\n` per event.

Event format. id: (event ID for reconnect), event: (type), data: (payload), retry: (reconnect delay).

Reconnect. Browser auto-reconnects on drop; sends Last-Event-ID header. Server can resume.

One-way server → client. Client can't send via SSE; use separate HTTP for control.

HTTP/2 friendly. No connection-per-stream limit; multiplex.

Firewall friendly. Standard HTTP; proxies pass.

Auth via headers or cookies. Cookies work via EventSource; custom headers require fetch-based polyfill.

LLM token streaming. Primary modern use case.

Browser EventSourceopens HTTP GETServertext/event-streamStreamed eventsdata: ...Event formatid, event, data, retryReconnectbrowser auto with Last-Event-IDOne-way server → clientno client sendHTTP/2 friendlymultiplex OKFirewall friendlyvs WebSocketAuth via headers or cookiesstandardLLM token streamingprimary use caseSimplest streaming for browser; used by OpenAI + Anthropic APIs
Server-Sent Events architecture: browser EventSource → server text/event-stream → streamed events with reconnect + Last-Event-ID.
Advertisement

End-to-end SSE flow

Trace an SSE call. Browser: `const es = new EventSource('/chat/stream?session=abc');`

Server accepts GET, sets Content-Type: text/event-stream. Doesn't close response.

LLM generates tokens. Server writes `id: 1\ndata: {"token": "Hello"}\n\n`. Flushes.

Browser receives; onmessage fires with parsed data. UI renders "Hello".

More tokens. Server keeps sending. Browser keeps rendering.

Network drop. Browser detects; waits `retry` ms; reconnects with `Last-Event-ID: 5` header.

Server sees resume; sends messages 6 onward.

Complete: server sends `event: done\ndata: {}\n\n` and closes.

Browser onerror fires; es.close() called to prevent auto-reconnect.