LEARNING OBJECTIVES โต
- Understand the exact UTF-8 stream specification for
text/event-stream. - Master the four standardized SSE fields:
data:,event:,id:, andretry:. - Parse multi-line payloads using consecutive
data:field declarations. - Utilize comment lines (
: keepalive ping) to prevent intermediate proxy timeout terminations. - Demystify the double-newline (
\n\n/\r\n\r\n) frame boundary delimiter.
๐ The Mental Model & Story (Intuitive Foundation)
Imagine an old-fashioned telegraph ticker tape machine printing continuous paper strips in a busy 19th-century stock exchange.
......................................................................
: this is a comment ticker heartbeat \n
id: 101 \n
event: price_update \n
data: {"symbol": "NVDA", \n
data: "price": 128.50} \n
\n
......................................................................
The ticker machine operates with simple, unambiguous rules:
- It reads line by line until it encounters two consecutive blank lines (a blank space on the tape,
\n\n). - A single blank line means "keep reading more fields for the current dispatch".
- A double blank line (
\n\n) means: "Dispatch this complete message now!" - If a line starts with a colon (
:), the machine ignores it as an internal operator note (heartbeat). - If multiple
data:lines arrive before the double newline, the machine glues them together with a line break (\n).
This simple, human-readable text framing format is what makes Server-Sent Events so lightweight, easy to debug in Wireshark or browser DevTools, and completely devoid of binary packing overhead.
Technical Deep Dive & Specifications
The Four Standard Protocol Fields
The WHATWG specification defines four valid field names in the text/event-stream wire format. Any unrecognized field name is silently ignored.
+-----------------------------------------------------------------------------------------------+
| SSE WIRE FORMAT FIELDS |
+-----------------------------------------------------------------------------------------------+
| Field Name | Example | Description |
+--------------+-----------------------------+--------------------------------------------------+
| data | data: {"status": "ok"} | The payload data. Multiple lines are joined by \n |
| event | event: user_joined | Custom event type (dispatched via addEventListener) |
| id | id: evt-98234 | Event ID. Updates the client's lastEventId |
| retry | retry: 5000 | Reconnection backoff interval in milliseconds |
| : (Comment) | : heartbeat ping | Ignored by parser; keeps idle sockets alive |
+-----------------------------------------------------------------------------------------------+
1. The data: Field & Multi-Line Concatenation
The data: field carries the actual message payload string. If your payload contains newlines (such as multiline text, formatted JSON, or Markdown), each line is prefixed with data: :
data: Line 1 of message
data: Line 2 of message
data: Line 3 of message
Parser Resulting event.data:
"Line 1 of message\nLine 2 of message\nLine 3 of message"
2. The event: Field (Custom Event Types)
Sets the event name. If omitted, the browser defaults to dispatching a generic message event (which triggers eventSource.onmessage). If specified, it must be listened to using addEventListener:
event: alert
data: Warning: High temperature!
eventSource.addEventListener('alert', (e) => {
console.log(e.data); // "Warning: High temperature!"
});
3. The id: Field (Event Resumption Identifier)
Sets the internal lastEventId property of the EventSource object. If the network connection drops, the browser sends this value in the Last-Event-ID request header upon reconnecting:
id: 42
data: Transaction #42 committed
4. The retry: Field (Client Backoff Directive)
Instructs the browser how many milliseconds to wait before attempting to reconnect if the connection drops:
retry: 10000
data: Reconnection wait time set to 10 seconds
5. Comments (:) & Proxy Heartbeats
Any line starting with a colon character (:) is treated as a comment and ignored by the browser. This is essential for sending periodic keep-alive pings (every 15โ30 seconds) to prevent firewalls and Nginx proxies from closing idle connections:
: ping heartbeat 2026-08-21T02:30:00Z
Strict Delimiter Rules: Single vs. Double Newlines
+-------------------------------------------------------------+
| data: Hello |
| data: World |
| \n | <-- Single newline continues message
| id: 1 |
| \n\n | <-- DOUBLE NEWLINE TRIGGERS DISPATCH!
+-------------------------------------------------------------+
๐ป Interactive Code Playground
Below is a live, interactive SSE Wire-Format Parser and Simulator. You can type or modify raw wire-format text in real time and observe how the parser state machine tokenizes and emits structured events.
Starter Code
Line-by-Line Code Breakdown
- Lines 105โ109: Normalizes all CRLF (
\r\n) and CR (\r) line breaks to standard LF (\n) for cross-platform parser consistency. - Lines 118โ137 (
dispatch()): Glues all lines indataBuffertogether with\nand creates a visual event card. Ifevent:was omitted,currentEvent.typedefaults to"message". - Lines 144โ147: When a blank line (
line === '') is encountered, the frame is complete anddispatch()is immediately executed. - Lines 150โ156: Lines beginning with
:are identified as keep-alive comments. They are filtered out and not sent toonmessage. - Lines 163โ170: Strict WHATWG spec rule: if the character immediately following the colon is a space (
U+0020), exactly one space is stripped. - Lines 172โ180: Distributes parsed fields to
data,event,id, orretry.
Expected Browser Render Output
๐ฌ Live SSE Wire Format Parser
Dispatched Event Output:
[Comment/Heartbeat] ping keepalive 10:00:00
[message] ID: 101 Retry: 5000ms
{"user": "Alice", "status": "online"}
[trade] ID: 102
{
"symbol": "BTC/USD",
"price": 64250.00
}
[Comment/Heartbeat] another comment
[system_alert] ID: 103
Emergency maintenance scheduled at midnight.๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Build a Backend Server Wire-Format Serializer
Instructions:
- Create a pure JavaScript utility function
formatSSEMessage(options)that returns a valid, RFC-compliant SSE string. - The options object must accept:
data: string or JavaScript object (if object, auto-serialize withJSON.stringify(), and properly handle multiline indentation!).event: optional string (e.g.'notification').id: optional string or number.retry: optional number in milliseconds.comment: optional string.
- Validate that your output ends with the mandatory
\n\n.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Sending a Single Newline (
\n) Instead of Double (\n\n): A single newline at the end of a message leaves the parser in a "waiting for more fields" state. The browser will not fireonmessageuntil the next frame arrives with a second newline. - Forgetting the Space After the Colon (
data: payload): Whiledata:payloadis valid according to the spec, the standard convention isdata: payload(with one space). If your server sendsdata: payload(two spaces), the browser strips only the first space, leaving a leading space inevent.data. - Sending Binary Data Directly: SSE is strictly a UTF-8 text protocol. Attempting to send raw binary buffers (e.g. PNG bytes or Protobuf) will cause decoding errors. Binary data must be Base64-encoded before transmitting over SSE.
๐ก Pro Tips
- Heartbeats Prevent Reverse Proxy Timeout: Load balancers (such as AWS ALB, Cloudflare, and Nginx) kill idle HTTP connections after 60 seconds of silence. Transmitting a
: ping\n\ncomment line every 15 to 25 seconds keeps intermediate proxy NAT tables warm without triggering any client-side JavaScript events. - JSON Payloads on Single Lines for Speed: While multi-line
data:is supported, serialization performance is highest when sending minified single-line JSON (JSON.stringify(payload)).
๐ Key Takeaways
- The SSE wire format is simple, human-readable UTF-8 text framed by
\n\n. - The 4 valid fields are
data:,event:,id:, andretry:. - Lines beginning with a colon (
:) are comments used for proxy keep-alive heartbeats. - Multi-line payloads are created by declaring multiple consecutive
data:lines. - The
id:field updates the browser's internallastEventIdfor automated reconnection recovery. - --