Streamed events arrive in bursts or only at the end
“Locally, server-sent events or a streamed response appear one by one. In production nothing shows for seconds, then a batch lands at once, or everything appears only when the stream closes.”
THE PRINCIPLE
A stream is only as live as the most eager buffer on its path; find each one and opt that route out.
FIRST MOVES
- Bisect the path with curl -N against the app port, then the proxy, then the public URL; the first hop that bursts is the culprit.
- nginx buffers proxied responses by default (proxy_buffering on). Send X-Accel-Buffering: no from the app or set proxy_buffering off on that location.
- Exclude the stream from compression or flush after each event: Express compression waits for a full window unless you call res.flush().
- Send Cache-Control: no-cache, no-transform so CDNs and proxies that honor it don't re-encode the body (Cloudflare skips compression for no-transform).
- Write the headers and a first comment line immediately, then a comment every ~15s so idle proxies don't cut the stream.
TOOLS: THEN → NOW
Global proxy_buffering offPer-response X-Accel-Buffering: no, so other routes keep buffering
Blanket compression middlewareCompression filter that skips text/event-stream, or explicit flushes
PATTERN SNAPSHOT
app.get("/events", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
"X-Accel-Buffering": "no", // nginx: don't buffer this response
});
res.write(": connected\n\n"); // first bytes out immediately
const keepAlive = setInterval(() => res.write(": ping\n\n"), 15_000);
const off = events.on((e) => res.write(`id: ${e.id}\ndata: ${JSON.stringify(e)}\n\n`));
req.on("close", () => { clearInterval(keepAlive); off(); });
});CLOSE THE AI. EXPLAIN THIS.
Your SSE endpoint streams fine on localhost but arrives in bursts behind nginx and gzip. Which two buffers are most likely, and how do you disable each for only that route?HOW IT WORKS UNDERNEATH
RELATED SYMPTOMS
SOURCES
Guide reviewed