text/event-stream
Server-sent eventsstreaming response
text/event-streamServer-sent events: a long-lived response in which the server pushes messages as lines of text, and the browser's EventSource reads them as they arrive.
| MIME type | text/event-stream |
|---|---|
| Used for | Streaming response |
| Parameters | charset — always UTF-8, so it can be left out |
| Specification | HTML Standard: server-sent events |
What it looks like on the wire
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
retry: 5000
event: price
id: 41
data: {"symbol":"BTC","usd":67210}
: keep-alive comment
data: a message with the default event type
Content-Type: text/event-stream
Send it from the server
app.get("/events", (req, res) => {
res.set({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache" });
res.flushHeaders();
const timer = setInterval(() => res.write(`data: ${Date.now()}\n\n`), 1000);
req.on("close", () => clearInterval(timer));
});export async function GET() {
const enc = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(enc.encode("data: hello\n\n"));
},
});
return new Response(stream, {
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
});
}location /events {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # or send X-Accel-Buffering: no from the app
proxy_read_timeout 1h;
}Read it in the client
const events = new EventSource("/events");
events.onmessage = (e) => console.log(e.data);
events.addEventListener("price", (e) => console.log(JSON.parse(e.data)));curl -N https://example.com/events
Good to know
- Reverse proxies buffer responses by default, so events arrive in bursts or only when the stream ends. Turn buffering off for the route —
proxy_buffering offor anX-Accel-Buffering: noresponse header for nginx — and make sure compression middleware flushes after each event or skips the stream. - EventSource reconnects on its own and sends the last
id:it received in aLast-Event-IDrequest header, so the server can resume where it left off. - EventSource can only make GET requests and can't set headers. AI APIs that stream over POST use the same event format, but clients read it with
fetchand a stream reader instead. - Over HTTP/1.1, browsers allow about six connections per origin, and every open EventSource holds one. HTTP/2 removes that limit.
Frequently asked questions
What is the difference between server-sent events and WebSockets?
Server-sent events are one-way, from server to client, over an ordinary HTTP response, with reconnection built in. WebSockets are two-way and need an upgrade handshake. For notifications, live feeds and streamed AI responses, server-sent events are simpler.
Why do my server-sent events arrive all at once?
Something between the server and the browser is buffering the response, usually a reverse proxy or compression middleware. Disable buffering for the stream (X-Accel-Buffering: no for nginx) and flush after each event.
What do the lines in an event stream mean?
data: carries the payload (several data lines are joined with newlines), event: names the event type, id: sets the ID used for resuming, retry: sets the reconnection delay in milliseconds, and a line starting with : is a comment, often sent as a keep-alive. A blank line ends each event.
Headers that go with it: Cache-Control · Content-Type · Content-Encoding