--- researched: 2025-01-20 domain: WebSocket and Server-Sent Events for real-time updates confidence: HIGH --- # Phase 2: Real-time Features - Research ## User Constraints (from CONTEXT.md) ### Locked Decisions - Use Server-Sent Events (not WebSockets) for dashboard updates - Must work behind Cloudflare proxy ### Claude's Discretion - Choice of SSE library - Event structure and naming ### Deferred Ideas (OUT OF SCOPE) - Bi-directional chat feature (future milestone) - Push notifications ## Summary Researched Server-Sent Events for real-time dashboard updates in Next.js. SSE is simpler than WebSockets for unidirectional server-to-client streaming and works reliably behind Cloudflare. Key finding: Next.js App Router supports SSE via ReadableStream in route handlers. No additional libraries needed - native EventSource on client, ReadableStream on server. **Primary recommendation:** Use native SSE with Next.js route handlers. Implement heartbeat to detect disconnections. Use EventSource with reconnection logic on client. ## Standard Stack ### Core | Library | Version | Purpose | Why Standard | |---------|---------|---------|--------------| | Native EventSource | - | Client-side SSE | Built into browsers, no deps | | ReadableStream | - | Server-side SSE | Built into Next.js/Node | ### Supporting | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | eventsource | 2.0.2 | Node SSE client | If testing SSE endpoints | | reconnecting-eventsource | 1.6.2 | Auto-reconnect | Production client resilience | ### Alternatives Considered | Instead of | Could Use | Tradeoff | |------------|-----------|----------| | Native SSE | socket.io | Socket.io if bidirectional needed, heavier | | Native SSE | Pusher | Pusher for scale, but adds dependency | **Installation:** ```bash npm install reconnecting-eventsource ``` ## Architecture Patterns ### Recommended Project Structure ``` src/ ├── app/api/events/ │ └── route.ts # SSE endpoint ├── hooks/ │ └── useSSE.ts # Client hook └── lib/ └── events.ts # Event types and helpers ``` ### Pattern 1: SSE Route Handler **What:** Next.js route handler returning SSE stream **When to use:** Any server-to-client streaming **Example:** ```typescript // Source: Next.js App Router docs export async function GET() { const stream = new ReadableStream({ start(controller) { const encoder = new TextEncoder(); const send = (data: object) => { controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); }; // Heartbeat every 30s const heartbeat = setInterval(() => send({ type: 'heartbeat' }), 30000); // Cleanup on close return () => clearInterval(heartbeat); } }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }); } ``` ### Pattern 2: Client Hook with Reconnection **What:** React hook that manages SSE connection lifecycle **When to use:** Any component consuming SSE **Example:** ```typescript // Source: Community pattern, verified function useSSE(url: string) { const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { const es = new EventSource(url); es.onmessage = (e) => setData(JSON.parse(e.data)); es.onerror = () => setError(new Error('SSE connection failed')); return () => es.close(); }, [url]); return { data, error }; } ``` ### Anti-Patterns to Avoid - **Polling instead of SSE:** SSE is more efficient for real-time - **No heartbeat:** Connections die silently without heartbeat - **Missing reconnection:** EventSource auto-reconnects but custom logic may be needed ## Don't Hand-Roll | Problem | Don't Build | Use Instead | Why | |---------|-------------|-------------|-----| | SSE parsing | Custom parser | Native EventSource | Browser handles format | | Reconnection | setTimeout loops | reconnecting-eventsource | Handles edge cases | | Event typing | Inline types | Shared types file | Type safety across client/server | **Key insight:** SSE is standardized. The browser's EventSource handles parsing, reconnection, and error recovery. Custom implementations miss edge cases. ## Common Pitfalls ### Pitfall 1: Cloudflare Buffering **What goes wrong:** Events don't arrive in real-time, come in batches **Why it happens:** Cloudflare buffers responses by default **How to avoid:** Set `Cache-Control: no-cache` and `X-Accel-Buffering: no` **Warning signs:** Events arrive in bursts, not individually ### Pitfall 2: Connection Limits **What goes wrong:** Browser limits SSE connections to same origin **Why it happens:** Browsers limit ~6 concurrent connections per domain **How to avoid:** Single SSE connection multiplexing multiple event types **Warning signs:** Later connections fail or queue ### Pitfall 3: Memory Leaks **What goes wrong:** Server memory grows over time **Why it happens:** Event handlers not cleaned up on disconnect **How to avoid:** Track connections, clean up on close/error **Warning signs:** Server memory increases with usage ## Code Examples ### Complete SSE Endpoint ```typescript // Source: Next.js docs + community best practices import { NextRequest } from 'next/server'; export const runtime = 'edge'; // Better for SSE export async function GET(req: NextRequest) { const stream = new ReadableStream({ start(controller) { const encoder = new TextEncoder(); const send = (event: string, data: unknown) => { controller.enqueue( encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`) ); }; // Initial data send('init', { connected: true }); // Heartbeat const interval = setInterval(() => send('heartbeat', {}), 30000); // Simulate updates const updates = setInterval(() => { send('update', { timestamp: Date.now() }); }, 5000); req.signal.addEventListener('abort', () => { clearInterval(interval); clearInterval(updates); }); } }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache, no-transform', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no' } }); } ``` ## State of the Art (2024-2025) | Old Approach | Current Approach | When Changed | Impact | |--------------|------------------|--------------|--------| | Pages API SSE | App Router + ReadableStream | 2023 | Cleaner API, Edge support | | socket.io default | SSE for unidirectional | 2022 | Simpler, less overhead | **New tools/patterns to consider:** - **Edge Runtime:** Better for long-lived connections - **React Server Components:** Can coexist with SSE for initial data **Deprecated/outdated:** - **res.write() in Pages API:** Use ReadableStream in App Router ## Open Questions 1. **Connection cleanup on Vercel** - What we know: Vercel has function timeout limits - What's unclear: Exact behavior of long-lived SSE on serverless - Recommendation: Test with 10-minute connections, implement client reconnection ## Sources ### Primary (HIGH confidence) - /vercel/next.js - App Router streaming, route handlers - MDN EventSource - Browser API reference ### Secondary (MEDIUM confidence) - Next.js GitHub discussions on SSE - verified against docs ### Tertiary (LOW confidence - needs validation) - None ## Metadata **Research scope:** - Core technology: Server-Sent Events - Ecosystem: Next.js App Router, Edge Runtime - Patterns: SSE routes, client hooks, reconnection - Pitfalls: Buffering, connection limits, memory **Confidence breakdown:** - Standard stack: HIGH - native browser/server APIs - Architecture: HIGH - verified with Next.js docs - Pitfalls: HIGH - documented in production apps - Code examples: HIGH - from official sources **Research date:** 2025-01-20 **Valid until:** 2025-02-20 (30 days - stable tech) --- *Phase: 02-realtime* *Research completed: 2025-01-20* *Ready for planning: yes*