TL;DR
- 結論: サーバーからクライアントへ一方向に流すだけなら SSE が第一候補。クライアント側からも頻繁に送る双方向通信が必要なら WebSocket を選ぶ。
- SSEの強み: 仕様レベルで自動再接続と
Last-Event-IDによる再開を備え、HTTPそのままなのでプロキシ・認証・HTTP/2との相性が良い。通知・ライブ更新・進捗ストリーミングに向く。 - WebSocketの強み: 全二重で低オーバーヘッド。チャット・共同編集・対戦ゲームなど、双方向かつ低レイテンシが要る場面で本領を発揮する。
- 落とし穴: SSEはHTTP/1.1だと同時接続数の制約に当たる。WebSocketは再接続・認証・水平スケール時のセッション管理を自前で設計する必要がある。
はじめに
こんにちは、みねです。
「リアルタイム通信を入れたい」という要件が来たとき、反射的に WebSocket を選んでいないでしょうか。実装してみると「クライアントからは何も送らないのに、再接続もスケールも全部自前で書くことになった」と後悔する——これはよくあるパターンです。
WebSocket と SSE(Server-Sent Events)は、どちらも「サーバーからの push」を実現しますが、設計思想が根本的に違います。本記事では両者の本質的な違いを通信モデルから再整理し、チャット・通知・ライブ更新・株価といったユースケース別に選択軸を示します。さらに、接続管理・再接続・スケール時に踏みやすい落とし穴まで実務視点でまとめます。
対象読者は、リアルタイム機能の技術選定を任されたフロントエンド/バックエンドエンジニアです。読み終えたとき、自分の要件でどちらを選ぶべきかを根拠つきで判断できる状態を目指します。
1. 本質的な違いは「方向」にある
最初に押さえるべきは、両者の通信方向です。
- WebSocket: 全二重(full-duplex)。ハンドシェイク後はクライアントとサーバーが対等に、いつでも双方向にメッセージを送れます。基盤は
ws:///wss://という独自プロトコルで、HTTP からアップグレードして確立します(仕様は RFC 6455)。 - SSE: 単方向(サーバー → クライアント)。クライアントは最初の HTTP リクエストを投げるだけで、以降はサーバーが
text/event-streamでイベントを流し続けます。クライアントから送りたいときは、別途ふつうの HTTP リクエストを使います。
この「方向」の違いが、後述するほぼすべての性質を決めます。SSE は HTTP の上にそのまま乗る薄い仕組みなので、HTTP のエコシステム(認証ヘッダ、プロキシ、圧縮、HTTP/2 多重化)をそのまま使えます。WebSocket は HTTP からアップグレードした後は別プロトコルになるため、HTTP の道具立てが効かなくなる場面があります。
一言でいえば、SSE は「HTTP の長い応答」、WebSocket は「HTTP の皮をかぶった別の TCP セッション」です。
MDN でも SSE は「サーバーからクライアントへの一方向チャネル」として明確に位置づけられています(MDN: Server-sent events)。WebSocket API の全体像は MDN: WebSocket を参照してください。
2. 主要な比較軸
実務で効く軸を表に整理します。
| 軸 | WebSocket | SSE |
|---|---|---|
| 通信方向 | 双方向(全二重) | 単方向(サーバー→クライアント) |
| プロトコル | ws/wss(RFC 6455) | HTTP(text/event-stream) |
| データ形式 | テキスト+バイナリ | テキスト(UTF-8)のみ |
| 自動再接続 | 仕様にはなし(自前) | 仕様で標準装備 |
| イベント再開 | 自前で実装 | Last-Event-ID で標準対応 |
| HTTP/2・認証との親和性 | 制約あり | 高い |
| ブラウザAPI | WebSocket | EventSource |
SSE がテキスト専用である点は、WHATWG の HTML 仕様でも text/event-stream として規定されています(WHATWG HTML: Server-sent events)。バイナリ(画像チャンク、Protocol Buffers など)を流したい場合は SSE は不向きで、WebSocket が選択肢になります。
なお、ブラウザの EventSource には withCredentials などのオプションはありますが、任意のリクエストヘッダを付けられないという制約があります[公式値]。トークンをヘッダで渡す設計の認証とは相性が悪く、Cookie かクエリパラメータでの認証が前提になりがちです。この点は後述の落とし穴で改めて触れます。
3. ユースケース別の選び方
チャット・共同編集 → WebSocket
ユーザーが頻繁にメッセージを送り、相手のメッセージもリアルタイムに受け取る——典型的な双方向ワークロードです。クライアント送信のたびに新しい HTTP リクエストを起こすのは無駄が大きく、全二重で常時つながっている WebSocket が自然です。共同編集(カーソル位置やオペレーションの相互送信)も同様です。
通知・フィード更新 → SSE
「新着通知が来たらバッジを出す」「タイムラインに新規投稿を差し込む」といった処理は、サーバーから push するだけで、クライアントからの送信は基本的にありません。ここで WebSocket を使うと、再接続もスケールも自前で抱えることになり割に合いません。SSE なら自動再接続が効き、HTTP のまま運用できます。
ライブ更新・進捗ストリーミング → SSE
LLM の逐次トークン出力、ビルド・デプロイの進捗ログ、長時間バッチの状態表示などは、サーバーが断続的にテキストを吐き出すパターンで、SSE の得意領域です。実際、多くの LLM API のストリーミングは SSE(text/event-stream)で実装されています。
株価・スコアボードなど高頻度配信 → 要件で分岐
「サーバーから流すだけ」なら SSE で十分です。ただし、ユーザーが銘柄の購読/解除を頻繁に送り返す、レイテンシ要件が極端に厳しい、バイナリで詰めて帯域を削りたい——こうした要件が重なるなら WebSocket に寄せます。
判断のコツは、**「クライアントから能動的に、かつ高頻度で送る必要があるか」**の一点です。Yes なら WebSocket、No なら SSE を起点に検討すると、選定がぶれません[経験則]。
4. コードで見る実装の違い
同じ「サーバーからの push を受ける」処理を、両者で書き比べます。
// --- SSE: クライアント ---
// EventSource が再接続・Last-Event-ID を自動で面倒見てくれる
const es = new EventSource("/api/stream");
es.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
console.log("受信:", data);
});
es.addEventListener("error", () => {
// 接続断時はブラウザが自動で再接続を試みる(実装不要)
console.warn("一時的に切断、自動再接続待ち");
});
// --- SSE: サーバー(Node.js / Express 風)---
function sseHandler(req, res) {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
let id = Number(req.headers["last-event-id"] ?? 0);
const timer = setInterval(() => {
// id 行を付けると再接続時に Last-Event-ID で再開できる
res.write(`id: ${++id}\n`);
res.write(`data: ${JSON.stringify({ id, ts: Date.now() })}\n\n`);
}, 1000);
req.on("close", () => clearInterval(timer));
}
// --- WebSocket: クライアント ---
// 再接続は仕様に無いため自前で実装する必要がある
function connect() {
const ws = new WebSocket("wss://example.com/socket");
ws.onmessage = (e) => console.log("受信:", JSON.parse(e.data));
ws.onopen = () => ws.send(JSON.stringify({ type: "subscribe", room: "general" }));
ws.onclose = () => {
// 自動再接続は無い → バックオフ付きで自前リトライ
setTimeout(connect, 1000);
};
}
connect();
// --- WebSocket: サーバー(ws ライブラリ風)---
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (socket) => {
socket.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
// クライアントからの送信を受けて双方向にやり取りできる
socket.send(JSON.stringify({ ack: msg.type }));
});
});
注目すべきは、SSE 側は再接続コードを一切書いていない点です。EventSource が切断検知・再接続・Last-Event-ID の送出を仕様として面倒みます。WebSocket 側はバックオフ付きの再接続を自分で書いており、本番ではさらに最大リトライ回数・ジッタ・サーバー側のセッション復元まで考慮が必要になります。
5. 運用で踏む落とし穴
5-1. SSE: HTTP/1.1 の同時接続数制限
ブラウザは同一オリジンに対する HTTP/1.1 の同時接続数を 6 程度に制限します[公式値]。SSE は接続を開きっぱなしにするため、複数タブ × 複数 SSE で容易に上限へ達し、新しいリクエストがブロックされます。対策は HTTP/2 以降を使うことです。HTTP/2 では 1 本の TCP コネクションを多重化するため、この制限を実質的に回避できます。SSE を採用するなら HTTP/2 配信をほぼ前提にすると安全です。
5-2. SSE: ヘッダ認証ができない
前述のとおり EventSource は任意ヘッダを付けられません。Authorization: Bearer ... を前提にした認証基盤とは噛み合わないため、Cookie 認証にするか、初回トークンをクエリで渡して短命セッションに交換するなどの設計が要ります。トークンを URL に載せるとアクセスログに残るリスクがあるので、扱いには注意します。
5-3. WebSocket: 再接続とメッセージ欠落
WebSocket には再接続もイベント再開の仕組みもありません。切断中に発生したメッセージは黙って失われます。SSE の Last-Event-ID に相当する仕組み(最後に受信したシーケンス番号をサーバーに伝えて差分を再送する)を、アプリ側で設計する必要があります。
5-4. WebSocket: 水平スケール時のブロードキャスト
複数サーバーに負荷分散すると、「サーバー A につないだユーザーへの通知を、サーバー B が受け取ったイベントから送る」必要が出ます。WebSocket は接続がサーバーに固定されるため、Redis Pub/Sub などのバックプレーンでサーバー間を中継する構成が定番です。SSE でも push 元の共有は必要ですが、ステートレスな HTTP に寄せやすいぶん、ロードバランサ/プロキシとの相性で悩みにくい傾向があります。プロキシ経由の WebSocket は Upgrade ヘッダの透過設定を忘れると接続できないことがあるため、インフラ側の確認も欠かせません。
5-5. 共通: アイドル接続のタイムアウト
ロードバランサや中間プロキシは、一定時間無通信の接続を切ることがあります。SSE はコメント行(:keep-alive\n\n)、WebSocket は ping/pong フレームを定期送信して、接続を維持するのが定石です。
6. 決定の指針
実務での判断は、次の順で詰めると迷いません。
- クライアントから高頻度に送るか? Yes → WebSocket。No → 次へ。
- バイナリを流すか? Yes → WebSocket。No → 次へ。
- それ以外 → SSE を第一候補にし、HTTP/2 配信を前提に組む。
迷ったら SSE から始めるのが安全です。要件が双方向へ育ったときに WebSocket へ移行するほうが、最初から WebSocket を抱えて再接続・スケールの複雑さを背負うより、トータルのコストは小さく収まります[経験則]。
関連して、エッジ環境での常時接続の扱いは Edge Functions採用の判断軸とアンチパターン で触れた制約とも関わります。フロントエンド全体のパフォーマンス優先順位づけは フロントエンドのパフォーマンス改善の優先順位 を参照してください。
まとめ
- WebSocket と SSE の違いは「双方向か単方向か」に集約され、そこからプロトコル・再接続・スケール特性が派生します。
- サーバーからの push が主役なら SSE、クライアント送信が頻繁なら WebSocket、という起点で選ぶとぶれません。
- 次のアクション: 自分の要件を §6 の 3 ステップに通し、SSE を選ぶなら HTTP/2 配信を、WebSocket を選ぶなら再接続・スケール設計を最初に確保してください。
FAQ
Q1. SSE はもう古い技術ですか? いいえ。HTTP/2 普及で同時接続数の弱点が薄れ、LLM のストリーミング応答などでむしろ採用が広がっています。単方向 push なら現役の第一候補です。
Q2. WebSocket で単方向の通知だけを送るのは間違いですか? 間違いではありませんが過剰になりがちです。クライアント送信が不要なら、自動再接続や HTTP 親和性で SSE のほうが運用が軽く済みます。
Q3. SSE でリクエストヘッダに認証トークンを付けられますか?
ブラウザの EventSource では任意ヘッダを付けられません。Cookie 認証や、初回トークンをクエリで渡して短命セッションに交換する設計を検討してください。
Q4. WebSocket は HTTP/2 で使えますか? RFC 8441 で HTTP/2 上の WebSocket(ブートストラップ)が定義されていますが、対応状況は環境差があります。導入前にプロキシ・サーバーの対応を確認してください。
Q5. 大量同時接続にはどちらが有利ですか? 一概には言えません。SSE は HTTP/2 多重化で接続を束ねやすく、WebSocket は接続あたりのオーバーヘッドが小さい一方でサーバー側のセッション管理コストが乗ります。要件次第です。
