The app hangs once users open a few tabs
“With one or two tabs everything works. Open a sixth or seventh and new tabs spin forever: API calls sit as Pending in DevTools, and closing an old tab instantly unblocks them.”
THE PRINCIPLE
Long-lived requests consume a shared, per-origin connection budget; count them across tabs, not per page.
FIRST MOVES
- Check the Protocol column in DevTools Network for the stream: http/1.1 means you're on the 6-connection limit. Look for requests stalled in the Queueing phase.
- Serve the stream origin over HTTP/2 or HTTP/3 at the hop the browser talks to (CDN, load balancer, or proxy); streams then multiplex on one connection (the concurrent-stream limit is negotiated, commonly 100).
- Share one connection across tabs: elect a leader tab with the Web Locks API and fan events out with BroadcastChannel, or hold the EventSource in a SharedWorker.
- Close streams in hidden tabs when freshness doesn't matter there, and reopen them on visibilitychange. A new EventSource doesn't send Last-Event-ID (only its own automatic reconnects do), so pass the last seen ID in the URL to catch up.
TOOLS: THEN → NOW
Separate subdomains to get more HTTP/1.1 connectionsHTTP/2 or HTTP/3 multiplexing on one origin
One EventSource per tabLeader tab via Web Locks plus BroadcastChannel, or a SharedWorker (Baseline since 2026)
PATTERN SNAPSHOT
const channel = new BroadcastChannel("live-events");
channel.onmessage = (e) => render(e.data); // followers just listen
// Only one tab per origin holds the lock; the next one takes over if it closes.
navigator.locks.request("live-events-leader", () => new Promise<never>(() => {
const source = new EventSource("/events");
source.onmessage = (e) => {
const data = JSON.parse(e.data);
render(data); // BroadcastChannel doesn't echo to the sender
channel.postMessage(data);
};
}));CLOSE THE AI. EXPLAIN THIS.
Why can a seventh tab break ordinary fetch() calls even though only the SSE stream holds connections open?HOW IT WORKS UNDERNEATH
RELATED SYMPTOMS
SOURCES
Guide reviewed