← Back to the library
FrontendApplied · 7 min

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

  1. 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.
  2. 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.
  3. Exclude the stream from compression or flush after each event: Express compression waits for a full window unless you call res.flush().
  4. 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).
  5. 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

SOURCES

Guide reviewed