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
- Client opens connection with EventSource
- Server sends events as data lines
- Browser automatically reconnects on disconnect
- 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:
- Use SSE for simple event streams with auto-reconnect
- Use fetch streams when you need custom headers or formats
- Use WebSockets for bidirectional communication
SSE is simpler and sufficient for most push-only scenarios like notifications, progress updates, and live dashboards.
