WebSockets vs Server-Sent Events: When to Use Which
WebSockets vs Server-Sent Events: When to Use Which
Both WebSockets and Server-Sent Events (SSE) enable real-time communication between server and client. Choosing between them depends on whether you need bidirectional communication or just server-to-client push.
Server-Sent Events (SSE)
SSE is a simple, HTTP-based protocol for server-to-client streaming. The client opens a connection, and the server pushes events through it.
Server Implementation
// app/api/events/route.ts (Next.js)
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Send an event every second
let count = 0;
const interval = setInterval(() => {
const data = JSON.stringify({ count: ++count, time: Date.now() });
controller.enqueue(encoder.encode(data: \${data}\n\n));
if (count >= 100) {
clearInterval(interval);
controller.close();
}
}, 1000);
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
Client Implementation
const eventSource = new EventSource("/api/events");
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data);
};
eventSource.onerror = () => {
console.log("Connection lost, reconnecting...");
// EventSource automatically reconnects
};
// Clean up
eventSource.close();
WebSockets
WebSockets provide full-duplex communication — both client and server can send messages at any time.
Server Implementation
// Using ws library
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
console.log("Client connected");
ws.on("message", (message) => {
const data = JSON.parse(message.toString());
// Echo to all connected clients
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
user: data.user,
message: data.message,
timestamp: Date.now(),
}));
}
});
});
ws.on("close", () => console.log("Client disconnected"));
});
Client Implementation
const ws = new WebSocket("wss://yourapp.com/ws");
ws.onopen = () => {
ws.send(JSON.stringify({ user: "Alice", message: "Hello!" }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(\${data.user}: \${data.message});
};
ws.onclose = () => {
// Must implement reconnection manually
setTimeout(() => connectWebSocket(), 1000);
};
Comparison
| Feature | SSE | WebSocket |
|---------|-----|-----------|
| Direction | Server to client only | Bidirectional |
| Protocol | HTTP | WS (upgrade from HTTP) |
| Auto-reconnect | Built-in | Manual |
| Binary data | No (text only) | Yes |
| Browser support | All modern browsers | All modern browsers |
| Through proxies/CDNs | Usually works | Can be problematic |
| Connection limit | 6 per domain (HTTP/1.1) | No browser limit |
| Complexity | Simple | More complex |
When to Use SSE
When to Use WebSockets
AI Streaming with SSE
Most LLM APIs use SSE for streaming responses:
export async function POST(request: Request) {
const { prompt } = await request.json();
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.ANTHROPIC_API_KEY!,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
stream: true,
messages: [{ role: "user", content: prompt }],
}),
});
// Forward the SSE stream to the client
return new Response(response.body, {
headers: { "Content-Type": "text/event-stream" },
});
}
Conclusion
Use SSE when you only need server-to-client push — it is simpler, works through CDNs, and auto-reconnects. Use WebSockets when both sides need to send messages in real time. For most web applications, SSE covers the real-time needs; WebSockets are for truly interactive, bidirectional use cases.