--- name: sse-websocket-streaming-expert description: "Expert guide for Server-Sent Events (SSE), WebSockets, and Streaming Architectures. Covers real-time data push, Socket.IO, Hono WebSocket, and AI response streaming / Panduan ahli streaming real-time." author: "Roedy Rustam" version: "4.4.2" --- # SSE, WebSocket & Streaming Expert (2026 Production Edition) [English](#english) | [Bahasa Indonesia](#bahasa-indonesia) --- ## English ### Orchestration & Integration Connects and orchestrates with domain skills: - **Backend & Runtimes**: `js-backend-expert` (Hono, Fastify, Express), `bun-runtime-expert` (native `Bun.serve` WebSockets), `cloudflare-workers-expert` (Durable Objects WebSockets), and `go-programming-expert` (goroutine channels). - **AI Streaming**: `vercel-ai-sdk-expert`, `gemini-agent-booster`, `voice-ai-realtime-agent`, and `chatbot-messaging-expert` for chunk-by-chunk token streaming and bidirectional audio/text. - **Frontend & Realtime**: `senior-frontend` (`EventSource` & `useChat` hooks), `realtime-collaboration-expert` (CRDTs), `reactive-sync-engine-expert`, and `api-gateway-proxy-expert` (unbuffered SSE reverse proxies). - **Lifecycle**: `brainstorming` and `zero-to-prod-orchestrator`. ### Description Production-grade engineering guide for real-time data push and bidirectional streaming architectures. Covers **Server-Sent Events (SSE)** with `Last-Event-ID` cursor replay, **WebSockets** on Hono/Bun and Cloudflare Durable Objects (WebSocket Hibernation API), horizontal scaling with **Redis Streams / Pub-Sub backplanes**, backpressure control via Web Streams (`TransformStream`), and nginx/Cloudflare reverse-proxy buffering configurations. ### Trigger Conditions - Building live notification feeds, financial order books, telemetry dashboards, or progress trackers. - Streaming LLM reasoning tokens (``) and structured tool deltas over HTTP SSE. - Implementing bidirectional chat, multiplayer rooms, or collaborative presence with WebSockets. - Scaling stateful WebSocket servers across multiple containers or serverless edge workers. --- ### Transport Selection Matrix | Transport | Direction | Protocol | Reconnection | Best Use Cases | | :--- | :--- | :--- | :--- | :--- | | **Server-Sent Events (SSE)** | Server $\rightarrow$ Client | Standard HTTP/2 & HTTP/3 | Native (`Last-Event-ID`) | AI token streaming, live notifications, status feeds, tickers | | **WebSockets** | Bidirectional (Full-Duplex) | `ws://` / `wss://` (TCP Upgrade) | Manual backoff + heartbeat | Chat rooms, multiplayer games, collaborative whiteboards | | **WebTransport** | Bidirectional (Streams + Datagrams) | HTTP/3 QUIC (`UDP`) | Application-managed | Low-latency game netcode, live media ingest | | **HTTP Chunked Stream** | Server $\rightarrow$ Client | `Transfer-Encoding: chunked` | Per-request retry | NDJSON bulk exports, React Server Component (RSC) flight streams | --- ### Core Implementation Patterns #### 1. Production SSE Server with `Last-Event-ID` Replay & Heartbeat (Hono / Next.js) Intermediate proxies (NGINX, Cloudflare, AWS ALB) drop idle HTTP connections after 60–100 seconds and may buffer chunks unless explicitly disabled: ```typescript // src/routes/sse-stream.ts import { Hono } from 'hono'; import { streamSSE } from 'hono/streaming'; const app = new Hono(); export interface StreamEventPayload { id: string; event: string; data: Record; } app.get('/api/stream/events', async (c) => { const lastEventId = c.req.header('Last-Event-ID') ?? c.req.query('cursor') ?? '0'; const tenantId = c.req.header('X-Tenant-Id'); if (!tenantId) { return c.json({ error: 'Missing X-Tenant-Id header' }, 401); } // Disable NGINX & proxy response buffering c.header('X-Accel-Buffering', 'no'); c.header('Cache-Control', 'no-cache, no-transform'); return streamSSE(c, async (stream) => { let closed = false; stream.onAbort(() => { closed = true; }); // 1. Replay missed events since lastEventId cursor from Redis Stream const missedEvents = await fetchMissedEventsFromStream(tenantId, lastEventId); for (const evt of missedEvents) { if (closed) return; await stream.writeSSE({ id: evt.id, event: evt.event, data: JSON.stringify(evt.data), retry: 3000, }); } // 2. Keep-alive heartbeat every 15s to prevent proxy idle timeout const heartbeatTimer = setInterval(async () => { if (closed) { clearInterval(heartbeatTimer); return; } await stream.writeSSE({ event: 'ping', data: String(Date.now()) }); }, 15_000); try { while (!closed) { await stream.sleep(1000); } } finally { clearInterval(heartbeatTimer); } }); }); async function fetchMissedEventsFromStream( tenantId: string, sinceId: string ): Promise { return [ { id: `${Date.now()}-0`, event: 'system.connected', data: { tenantId, resumedFrom: sinceId }, }, ]; } export default app; ``` --- #### 2. Horizontal WebSocket Scaling with Redis Pub/Sub & Heartbeat (Bun / Node.js) When scaling horizontally across multiple instances, Client A on Node 1 cannot message Client B on Node 2 without a shared Pub/Sub backplane: ```typescript // src/ws/realtime-hub.ts import Redis from 'ioredis'; export interface WsMetadata { userId: string; roomId: string; isAlive: boolean; } const pub = new Redis(process.env.REDIS_URL!); const sub = new Redis(process.env.REDIS_URL!); const localClients = new Map void; meta: WsMetadata }>>(); sub.on('message', (channel, message) => { if (!channel.startsWith('room:')) return; const roomId = channel.slice(5); const peers = localClients.get(roomId); if (!peers) return; for (const client of peers) { client.send(message); } }); export async function joinRoom( socket: { send: (msg: string) => void; meta: WsMetadata } ): Promise { const { roomId } = socket.meta; let roomSet = localClients.get(roomId); if (!roomSet) { roomSet = new Set(); localClients.set(roomId, roomSet); await sub.subscribe(`room:${roomId}`); } roomSet.add(socket); } export async function broadcastToRoom(roomId: string, payload: unknown): Promise { const serialized = JSON.stringify({ roomId, timestamp: new Date().toISOString(), payload, }); await pub.publish(`room:${roomId}`, serialized); } ``` --- #### 3. Resilient Browser SSE Client with Exponential Backoff (POST Streaming) Native `EventSource` only supports `GET` requests and cannot send custom `Authorization: Bearer` headers or JSON POST bodies (required for LLM chat streams): ```typescript // src/client/stream-post.ts export async function streamPostSSE(options: { url: string; body: Record; headers?: Record; signal?: AbortSignal; onEvent: (event: { event: string; data: string; id?: string }) => void; }): Promise { const response = await fetch(options.url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream', ...options.headers, }, body: JSON.stringify(options.body), signal: options.signal, }); if (!response.ok || !response.body) { throw new Error(`Stream connection failed with HTTP ${response.status}`); } const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); let buffer = ''; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += value; const frames = buffer.split('\n\n'); buffer = frames.pop() ?? ''; for (const frame of frames) { let eventName = 'message'; let dataLines: string[] = []; let eventId: string | undefined; for (const line of frame.split('\n')) { if (line.startsWith('event:')) eventName = line.slice(6).trim(); else if (line.startsWith('id:')) eventId = line.slice(3).trim(); else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim()); } if (dataLines.length > 0) { options.onEvent({ event: eventName, data: dataLines.join('\n'), id: eventId }); } } } } ``` --- ### Verification Checklist - [ ] SSE endpoints set `X-Accel-Buffering: no` and `Cache-Control: no-cache, no-transform` to prevent reverse-proxy buffering. - [ ] Idle connections emit a periodic `: ping` or `event: ping` frame every 15–25 seconds. - [ ] Reconnecting clients pass `Last-Event-ID` to resume missed events without data loss. - [ ] WebSocket servers enforce maximum payload size (`maxPayloadLength`) and rate-limit incoming frames per socket. - [ ] Multi-instance WebSocket deployments synchronize state via Redis Streams/Pub-Sub or Cloudflare Durable Objects. ### Sovereign Execution, Token Efficiency & Invariants | Dimension | Sovereign Mandate & Deterministic Contract | | :--- | :--- | | **Frontier Model Cascade** | Medium / High (`GPT-5.6 Sol Medium`, `Gemini 4 Argon`, `Claude Sonnet 5.5 High`, `Grok 4.7 High Fast`) | | **Availability Fallback** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Zero unhandled missing-model errors) | | **KV-Cache & Token Protocol** | Pin static instructions & schemas at prompt prefix (`kv-cache-prefix-optimizer`); pass surgical `CONTEXT_MAP.md` slices (<250 tokens/call) during Swarm Fan-Out | | **SaaT CLI & MCP Dispatch** | CLI: `vibes skill exec sse-websocket-streaming-expert ` / MCP: `vibes_skill_dispatch`, `vibes_ast_query`, `vibes_compile_cache`, `vibes_optimize_budget`, `vibes_verify_gate` | | **NFR & Anti-Slop Invariants** | P99 API Gateway Latency < 85ms, OpenAPI 3.1 / TypeSpec strict schema parity, RFC 9457 Problem Details error contracts, Idempotency-Key enforcement; 100% complete zero-placeholder output verified via `node scripts/check-anti-slop.js --strict` | --- --- ## Bahasa Indonesia ### Integrasi Orkestrasi Terhubung dan mengorkestrasi bersama skill domain: - **Backend & Runtime**: `js-backend-expert` (Hono, Fastify, Express), `bun-runtime-expert` (WebSocket native `Bun.serve`), `cloudflare-workers-expert` (WebSocket Durable Objects), dan `go-programming-expert` (channel konkurensi goroutine). - **Streaming AI**: `vercel-ai-sdk-expert`, `gemini-agent-booster`, `voice-ai-realtime-agent`, dan `chatbot-messaging-expert` untuk streaming token AI chunk-by-chunk dan komunikasi suara/teks dua arah. - **Frontend & Realtime**: `senior-frontend` (hook `EventSource` & `useChat`), `realtime-collaboration-expert` (CRDT Yjs), `reactive-sync-engine-expert`, dan `api-gateway-proxy-expert` (konfigurasi reverse proxy tanpa buffering streaming). - **Siklus Hidup**: `brainstorming` dan `zero-to-prod-orchestrator`. ### Deskripsi Panduan rekayasa kelas produksi untuk arsitektur push data real-time dan streaming dua arah. Mencakup **Server-Sent Events (SSE)** dengan pemutaran ulang kursor `Last-Event-ID`, **WebSockets** di Hono/Bun dan Cloudflare Durable Objects, penskalaan horizontal dengan **Redis Streams / Pub-Sub**, kontrol *backpressure*, serta konfigurasi anti-buffering reverse proxy. ### Panduan Arsitektur Inti 1. **SSE vs WebSockets**: Gunakan SSE untuk aliran satu arah dari server ke klien (streaming token AI, notifikasi live, feed harga) karena berjalan di atas HTTP/2 standar dan mendukung rekoneksi otomatis. Gunakan WebSockets untuk komunikasi dua arah berlatensi rendah (chat interaktif, multiplayer). 2. **Pencegahan Buffering Proxy**: Selalu kirimkan header `X-Accel-Buffering: no` dan `Cache-Control: no-cache, no-transform` serta sinyal *heartbeat* setiap 15 detik agar koneksi tidak diputus oleh NGINX atau Cloudflare. 3. **Penskalaan Horizontal (Redis Backplane)**: Gunakan Redis Pub/Sub atau Redis Streams agar pesan dari Server A dapat diteruskan ke klien yang terhubung di Server B, serta mendukung *replay* pesan menggunakan `Last-Event-ID`. ### Daftar Periksa Verifikasi (Checklist) - [ ] Endpoint SSE menyertakan header `X-Accel-Buffering: no` dan `Cache-Control: no-cache, no-transform`. - [ ] Koneksi menyertakan mekanisme *heartbeat* setiap 15–25 detik. - [ ] Rekoneksi klien mengirimkan `Last-Event-ID` untuk memulihkan pesan yang terlewat. - [ ] Server WebSocket membatasi ukuran frame maksimum dan memiliki *rate-limiter* per koneksi. ### Matriks Eksekusi Berdaulat, Efisiensi Token & Invarian | Dimensi | Mandat Berdaulat & Kontrak Deterministik | | :--- | :--- | | **Kaskade Model Frontier** | Medium / High (`GPT-5.6 Sol Medium`, `Gemini 4 Argon`, `Claude Sonnet 5.5 High`, `Grok 4.7 High Fast`) | | **Invarian Fallback Ketersediaan** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Bebas gagal saat model belum tersedia/kuota penuh) | | **Protokol Hemat Token & KV-Cache** | Kunci instruksi & skema statis di prefix awal (`kv-cache-prefix-optimizer`); kirim potongan `CONTEXT_MAP.md` (<250 token/panggilan) saat Fan-Out | | **Eksekusi SaaT CLI & MCP** | CLI: `vibes skill exec sse-websocket-streaming-expert ` / MCP: `vibes_skill_dispatch`, `vibes_ast_query`, `vibes_compile_cache`, `vibes_optimize_budget`, `vibes_verify_gate` | | **Invarian NFR & Anti-Slop** | Latensi P99 API < 85ms, paritas skema ketat OpenAPI 3.1 / TypeSpec, kontrak error RFC 9457 Problem Details, penegakan Idempotency-Key; kode 100% tuntas tanpa placeholder diverifikasi melalui `node scripts/check-anti-slop.js --strict` |