Skip to main content
Ganesh Joshi
Back to Blogs

HTTP streaming and Server-Sent Events for real-time updates

February 20, 20263 min read
Tips
HTTP streaming and Server-Sent Events data flow on screen

For one-way real-time updates (server to client), you can use HTTP streaming or Server-Sent Events (SSE) instead of WebSockets. Both work over HTTP and are simpler for push-only scenarios.

When to use what

Technology Direction Use case
SSE Server to Client Live updates, notifications, progress
WebSocket Bidirectional Chat, gaming, collaborative editing
Fetch streams Server to Client Custom formats, binary data

SSE is simpler than WebSockets for one-way push. The browser handles reconnection automatically.

Server-Sent Events basics

How SSE works

  1. Client opens connection with EventSource
  2. Server sends events as data lines
  3. Browser automatically reconnects on disconnect
  4. Connection stays open until closed

Event format

Events use a simple text format with data lines ending in double newlines.

Server implementation

Next.js Route Handler

export const runtime = 'edge';

export async function GET() {
  const encoder = new TextEncoder();
  
  const stream = new ReadableStream({
    async start(controller) {
      controller.enqueue(encoder.encode('data: Connected\n\n'));
      
      let count = 0;
      const interval = setInterval(() => {
        count++;
        const data = JSON.stringify({ count, timestamp: Date.now() });
        controller.enqueue(encoder.encode('data: ' + data + '\n\n'));
        
        if (count >= 10) {
          clearInterval(interval);
          controller.close();
        }
      }, 1000);
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

Client implementation

Using EventSource

useEffect(() => {
  const eventSource = new EventSource('/api/events');

  eventSource.onopen = () => {
    setConnected(true);
  };

  eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    setUpdates((prev) => [...prev, data]);
  };

  eventSource.onerror = () => {
    setConnected(false);
  };

  return () => {
    eventSource.close();
  };
}, []);

Fetch streaming

For more control or custom formats, use fetch with stream reading:

async function streamResponse() {
  const response = await fetch('/api/stream', {
    headers: {
      'Authorization': 'Bearer ' + token,
    },
  });

  const reader = response.body?.getReader();
  if (!reader) return;

  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    const chunk = decoder.decode(value);
    processChunk(chunk);
  }
}

Common patterns

Progress updates

Send progress events during long-running operations:

const stream = new ReadableStream({
  async start(controller) {
    const steps = ['Uploading', 'Processing', 'Optimizing', 'Complete'];
    
    for (let i = 0; i < steps.length; i++) {
      await processStep(i);
      const progress = ((i + 1) / steps.length) * 100;
      const event = 'data: ' + JSON.stringify({ step: steps[i], progress }) + '\n\n';
      controller.enqueue(encoder.encode(event));
    }
    controller.close();
  },
});

AI response streaming

Stream AI-generated text as it is produced:

const stream = new ReadableStream({
  async start(controller) {
    for await (const chunk of aiStream) {
      controller.enqueue(encoder.encode('data: ' + chunk + '\n\n'));
    }
    controller.close();
  },
});

Live logs

Display real-time log output:

function LogViewer() {
  const [logs, setLogs] = useState([]);

  useEffect(() => {
    const es = new EventSource('/api/logs');
    
    es.onmessage = (e) => {
      setLogs((prev) => [...prev.slice(-99), e.data]);
    };
    
    return () => es.close();
  }, []);

  return (
    <pre className="h-96 overflow-auto">
      {logs.map((log, i) => <div key={i}>{log}</div>)}
    </pre>
  );
}

Comparison

Feature SSE Fetch Streams WebSocket
Direction Server to Client Server to Client Bidirectional
Auto reconnect Yes No No
Custom headers Limited Yes On connect only
Binary data No Yes Yes
Browser support All modern All modern All modern
Complexity Low Medium Higher

Summary

For server-to-client real-time updates:

  1. Use SSE for simple event streams with auto-reconnect
  2. Use fetch streams when you need custom headers or formats
  3. Use WebSockets for bidirectional communication

SSE is simpler and sufficient for most push-only scenarios like notifications, progress updates, and live dashboards.

Frequently Asked Questions

SSE is a standard for one-way server-to-client real-time communication over HTTP. The server sends events; the browser automatically handles reconnection with EventSource.

Use SSE for one-way server-to-client push (notifications, live updates, progress). Use WebSockets when you need bidirectional communication (chat, gaming, collaborative editing).

Create a Route Handler that returns a streaming Response with Content-Type text/event-stream. Use ReadableStream to send events over time.

Yes. SSE works at the edge. Keep connections short-lived since edge has execution time limits. Use SSE for quick updates, not long-running connections.

EventSource does not support custom headers in all browsers. Options include passing tokens in the URL (with security considerations), using cookies, or using fetch with streams instead.

Related Posts