gotchatypescriptMajor
Server-Sent Events Are Unidirectional and HTTP/1.1 Connection-Limited
Viewed 0 times
SSEserver-sent eventsEventSourceHTTP/2connection limitreal-time streamtext/event-stream
Problem
Server-Sent Events (SSE) are chosen for real-time updates, but browsers open multiple SSE connections per page (tabs, iframes) and HTTP/1.1 has a 6 connection-per-origin limit, causing all connections to stall.
Solution
Serve SSE over HTTP/2 which has no per-origin connection limit. If HTTP/1.1 is unavoidable, use a single SharedWorker-based SSE connection shared across tabs.
// server: Express SSE endpoint
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
const sendEvent = (data: unknown) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
const unsubscribe = eventBus.subscribe(req.query.userId as string, sendEvent);
req.on('close', () => {
unsubscribe();
});
});
// Client: built-in automatic reconnection
const es = new EventSource('/events?userId=123');
es.onmessage = (e) => console.log(JSON.parse(e.data));
es.onerror = () => console.warn('SSE reconnecting...');Why
HTTP/1.1 browsers limit concurrent connections to 6 per origin. Each SSE stream holds one connection open. With multiple tabs open, new connections queue behind the SSE connections and appear to hang.
Gotchas
- SSE is text-only and unidirectional — use WebSockets or fetch for client-to-server messages.
- EventSource automatically reconnects using the Last-Event-ID header — always set event IDs for resumability.
- Nginx may buffer SSE responses — set X-Accel-Buffering: no header to disable it.
- SSE does not work through HTTP proxies that buffer responses without special configuration.
Revisions (0)
No revisions yet.