What SSE Comment Lines Are For Permalink to this section
Part of Understanding the Event Stream Format, under SSE Protocol Fundamentals & Architecture.
Any line in an event stream that starts with a colon is a comment. The parser discards it: it sets no field, dispatches no event, and never reaches JavaScript. That makes comments look useless, and they are in fact one of the most useful parts of the format — the only way to send bytes over a Server-Sent Events connection without the client application noticing. This guide covers what comments are good for, the one thing they cannot do, and how to use them well.
Symptom & Developer Intent Permalink to this section
Comments usually come up in one of these situations:
- Idle streams are being closed by a proxy or load balancer, and something must be sent to keep them open.
- A client watchdog built on
EventSourcenever sees the server’s heartbeats and reconnects healthy streams. - An old intermediary holds the first few kilobytes of every response, delaying the first event.
- An engineer debugging a stream with
curlwants to see what the server is doing between events. - A heartbeat implemented as
data: pingpollutes application listeners with meaningless messages.
The intent is to use comments for transport-level concerns, and events for anything the application must observe.
Root Cause Analysis Permalink to this section
The parsing rule is simple. For each line: if it is empty, dispatch; if it starts with :, ignore it; otherwise split on the first colon into field name and value.
Because comments change nothing, they are ideal for traffic that exists only for the network: keeping connections alive through idle timeouts, and exposing dead peers to the server when a write fails. For the same reason they are invisible to the client application. EventSource fires no event for a comment, so JavaScript cannot tell that one arrived.
Step-by-Step Resolution Permalink to this section
Step 1 — Use comments as keep-alive heartbeats Permalink to this section
// Send a comment only when the stream has been silent for the interval.
let idle;
const arm = () => { clearTimeout(idle); idle = setTimeout(() => { res.write(': hb\n\n'); arm(); }, 15000); };
arm();
function send(frame) { res.write(frame); arm(); }
A comment followed by a blank line is conventional (: hb\n\n); the blank line dispatches nothing because no data field was set, and it keeps frame boundaries tidy for tools. Choosing the interval is covered in choosing heartbeat intervals for SSE.
Step 2 — Use an event, not a comment, when the client must observe liveness Permalink to this section
A client silence watchdog needs something it can see. Send a tiny named event for that purpose, and keep it out of the application’s listeners by name:
event: hb
data:
let lastByte = Date.now();
es.addEventListener('hb', () => { lastByte = Date.now(); });
es.onmessage = () => { lastByte = Date.now(); };
setInterval(() => { if (Date.now() - lastByte > 45000) reconnect(); }, 5000);
Note that data: with an empty value still dispatches: an event with an empty data buffer is dispatched only if a data field was present, which it is. Clients that read the raw stream with fetch can see comments directly and do not need this.
Step 3 — Pad the start of the stream only if an intermediary needs it Permalink to this section
Some older proxies and a few antivirus products hold the first 1–4 KB of a response before forwarding anything. A single comment of padding at the start of the stream pushes past that threshold so the first real event is delivered immediately:
res.write(':' + ' '.repeat(2048) + '\n\n'); // one-time padding; costs 2 KB per connection
Use this only after confirming that such an intermediary exists in your users’ paths; fixing buffering properly is always better, as described in diagnosing buffered SSE output.
Step 4 — Leave debugging breadcrumbs Permalink to this section
Comments are a clean place for information that helps a human reading the raw stream:
: stream opened node=sse-7 region=eu-west-1 resume_from=8812 replayed=14
: replay complete, switching to live
id: 8827
event: order
data: {"id":"o_19f","status":"shipped"}
They cost a few bytes and never reach application code. Avoid putting secrets or personal data in them — they are visible to anyone with DevTools open.
Comments can also carry lightweight operational signals that tools, rather than the application, consume. A load-testing client or a synthetic monitor reading the raw stream can parse : srv-time 1726651200123 heartbeats to measure clock offset and delivery delay, without the production client ever seeing them. Keep a stable, documented format for such comments if tools depend on them, and remember that nothing guarantees delivery: a proxy may legitimately coalesce or, in rare transforming setups, strip them.
Step 5 — Parse comments correctly in custom clients Permalink to this section
A custom parser must treat any line whose first character is a colon as a comment — including a line that is just : — and must not treat a colon later in the line as making it a comment. data: a: b is a data field with value a: b.
Validation & Monitoring Permalink to this section
# See comments in the raw stream (EventSource never will).
curl -sN http://localhost:3000/events | grep --line-buffered '^:'
# Confirm the application ignores them: no message events for heartbeat-only periods.
In DevTools, the EventStream tab lists dispatched events only, so comments do not appear there either; use curl or the raw response view to see them.
Production Checklist Permalink to this section
Frequently Asked Questions Permalink to this section
Can JavaScript read comment lines with EventSource?
No. The parser discards them before dispatch. To observe them, read the stream yourself with fetch and a custom parser.
Does the text after the colon matter?
Not to the parser. Anything after the colon is ignored, so use it for human-readable labels or leave it empty.
Is a comment enough to keep an HTTP/2 stream alive?
Yes. A comment is ordinary response data, sent as DATA frames on HTTP/2, which every hop sees as activity on the stream.
Do comments count toward bandwidth costs?
Yes, like any bytes. A short heartbeat every fifteen seconds is well under one byte per second per connection, which is negligible for most services.