WebSocketとSSEの使い分け|チャット・通知・共同編集をリアルタイム実装する技術選定
コセケン
テクラル合同会社

リアルタイム機能を実装するときの第一原則は「通信が双方向でなければ WebSocket を選ばない」です。クライアントから高頻度でサーバーへ送り返す必要がある機能(チャット・共同編集・オンラインゲーム)は WebSocket、サーバーからの一方向配信で足りる機能(通知・進捗バー・AI のトークン逐次表示・株価フィード)は SSE(Server-Sent Events)が正解になります。両者は「リアルタイム」という言葉で一括りにされがちですが、運用コスト・障害時の挙動・スケール時の難易度がまったく異なり、ここを取り違えると本番運用で確実に高くつきます。
本記事では、WebSocket と SSE の技術的な違いを実装コード付きで整理したうえで、チャット・通知・共同編集という代表的な3つの機能ごとに「どちらを、なぜ選ぶか」を判断軸とともに示します。最後に、リアルタイム機能を内製するか外注するかを分ける見極めポイントまで踏み込みます。
結論:双方向なら WebSocket、サーバー押し出しだけなら SSE
技術選定の結論を先に示すと、判断軸は「クライアント→サーバーの送信頻度」と「運用負荷をどこまで許容できるか」の2つです。WebSocket は HTTP からアップグレードして専用の TCP 全二重チャネルを張り、双方向に低レイテンシで通信できますが、その代償として再接続・認証・スケールのロジックをすべて自前で書く必要があります。一方 SSE は閉じない HTTP レスポンスの上にテキストを流し続けるだけのシンプルな仕組みで、再接続はブラウザの EventSource が自動で面倒を見てくれます。
| 観点 | WebSocket | SSE(Server-Sent Events) |
|---|---|---|
| 通信方向 | 双方向(全二重) | サーバー→クライアントの一方向 |
| プロトコル | HTTP からアップグレードした専用 TCP | 標準 HTTP(閉じないレスポンス) |
| データ形式 | テキスト + バイナリ | UTF-8 テキストのみ |
| 自動再接続 | 自前実装が必要 | ブラウザ標準で自動 |
| ブラウザ API | WebSocket |
EventSource |
| インフラ親和性 | プロキシ・CDN で詰まりやすい | 普通の HTTP として扱える |
| 実装・運用コスト | 高い | 低い |
迷ったら SSE から始めるのが2026年時点での実務上の定石です。Ably の技術比較でも「サーバー押し出し用途では SSE が単純なデフォルト。両側が高頻度で送り合う場合に WebSocket が正しい選択」と整理されており(Ably: WebSockets vs SSE)、過剰に WebSocket を選ぶと運用負荷だけが増えます。
SSE の実装:自動再接続が最大の武器
SSE の実装は驚くほど薄く、サーバーは Content-Type: text/event-stream を返してテキストを流し続け、クライアントは EventSource で受けるだけです。最大の利点は、回線が一瞬切れてもブラウザが自動で再接続する点にあります。WebSocket では自分で書かなければならない再接続・バックオフのロジックが不要になります。
クライアント側は数行で完結します。
const source = new EventSource("/api/notifications");
source.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
console.log("通知:", data);
});
source.addEventListener("error", () => {
// ブラウザが自動で再接続する。手動の再接続処理は基本不要
});
サーバー側(Node.js / Express)も標準的な HTTP レスポンスとして書けます。
app.get("/api/notifications", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const timer = setInterval(() => {
// id を付けておくと、再接続時に Last-Event-ID で取りこぼしを補完できる
res.write(`id: ${Date.now()}\n`);
res.write(`data: ${JSON.stringify({ message: "新着あり" })}\n\n`);
}, 5000);
req.on("close", () => clearInterval(timer));
});
MDN のドキュメントによれば、各イベントには id を付けられ、接続が切れて再接続するとブラウザは自動的に Last-Event-ID ヘッダを送るため、サーバーは切断中に発生したイベントを補完して送り直せます(MDN: Using server-sent events)。再接続間隔も retry: フィールドでミリ秒指定できます。この「取りこぼしを防ぐ仕組みが標準装備」という点が、SSE を本番で安心して使える理由です。
SSE の落とし穴:HTTP/1.1 の6接続制限
SSE を本番投入する前に必ず潰しておくべき罠が、HTTP/1.1 環境での同時接続数制限です。MDN は明確に「HTTP/2 を使わない場合、SSE はブラウザあたり・ドメインあたり6接続という非常に低い上限に縛られる」と記載しています(MDN: Using server-sent events)。SSE のレスポンスは閉じないため、1接続が常時1枠を占有し続けます。ユーザーが複数タブを開くと、わずか数タブで枠を使い切り、新しい接続が張れなくなります。
解決策は HTTP/2 を有効にすることです。HTTP/2 では1本の TCP 接続上で多数のストリームを多重化でき、同時ストリーム数の上限はサーバーとクライアントの間で交渉され、既定値は100です。つまり実運用で SSE を採用するなら「HTTP/2 を前提にする」ことが事実上の必須条件になります。
| 環境 | SSE 同時接続の上限 | 実務での意味 |
|---|---|---|
| HTTP/1.1 | ドメインあたり6接続 | 複数タブで即枯渇。本番では避ける |
| HTTP/2 | 既定100ストリーム(交渉制) | 多重化で実用に耐える。SSE の前提条件 |
この制限を知らずに HTTP/1.1 のまま SSE を本番に出すと、「特定ユーザーだけ通知が来ない」という再現性の低い障害として表面化し、原因特定に時間を溶かします。リアルタイム機能の設計でインフラ(HTTP/2 終端・リバースプロキシのバッファリング設定)まで一体で詰める必要があるのは、こうした理由からです。
WebSocket の実装:双方向だが再接続は自前
WebSocket は双方向通信が必須の機能で選びます。クライアントとサーバーが対等に、いつでもメッセージを送り合える点が SSE との決定的な違いです。ただしその自由度の代償として、SSE が標準でやってくれる再接続を自分で実装しなければなりません。
クライアント側の最小実装は次のようになりますが、本番では再接続ロジックが必須です。
function connect() {
const ws = new WebSocket("wss://example.com/chat");
ws.addEventListener("open", () => {
ws.send(JSON.stringify({ type: "join", room: "general" }));
});
ws.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
renderMessage(msg);
});
ws.addEventListener("close", () => {
// SSE と違い、再接続とバックオフは自前で書く必要がある
setTimeout(connect, 1000);
});
return ws;
}
connect();
実際の本番運用ではこれに加えて、指数バックオフ、接続維持のための ping/pong ハートビート、認証トークンの再送、メッセージの順序保証、切断中に送れなかったメッセージのキューイングが必要になります。「動くデモ」と「本番に乗る WebSocket」の間にある実装量の差は、ここに集約されています。
チャット・通知・共同編集:機能別の選定
3つの代表機能を判断軸に当てはめると、選択は明確に分かれます。クライアントからの送信頻度と双方向性の有無が、そのまま技術選定を決めます。
| 機能 | 推奨 | 理由 |
|---|---|---|
| 通知・お知らせ配信 | SSE | サーバー→クライアントの一方向で十分。自動再接続が効く |
| AI のトークン逐次表示 | SSE | 生成結果を一方向で流すだけ。HTTP として CDN にも乗る |
| 進捗バー・ダッシュボード更新 | SSE | 読み取り専用のリアルタイム表示の典型 |
| チャット | WebSocket | 送受信が頻繁で双方向。低レイテンシが要る |
| 共同編集(同時編集) | WebSocket | 双方向 + 高頻度。競合解決の往復通信が必要 |
| オンラインゲーム・カーソル共有 | WebSocket | ミリ秒単位の双方向同期 |
注目すべきはAI チャットの「送信」と「応答ストリーミング」を分けて考える点です。ユーザーの入力送信は通常の HTTP POST、AI 応答のトークン逐次表示は SSE、という構成が広く使われています。会話形式の UI だからといって WebSocket が必要とは限りません。一方で、複数人が同じドキュメントを同時に編集する共同編集は、各自の変更を即座に全員へ反映し競合を解決する往復が不可欠なため、WebSocket がほぼ唯一の現実解になります。
機能名(チャットだから WebSocket)で短絡せず、「クライアントが高頻度で送り返すか」で判断するのが、過剰実装を避ける勘所です。
本番運用で効いてくるスケールの差
技術選定の真のコストは、初期実装ではなくスケール時に現れます。SSE は普通の HTTP として扱えるためロードバランサや CDN との相性がよく、スティッキーセッション(同一クライアントを同一サーバーに固定する設定)が必須ではありません。対して WebSocket を複数サーバーへ水平スケールさせると、設計の難易度が一段上がります。
WebSocket をスケールさせる際の典型的な構成は、サーバー間で Redis・NATS・Kafka などの Pub/Sub バックプレーンを挟み、どのサーバーに繋がったクライアントにもメッセージを配れるようにする方式です。さらにスティッキーセッションへの依存を減らすため、接続状態を Redis などの外部ストアに持たせ、再接続時にどのサーバーでも状態を復元できるようにします。加えて、クライアントが配信に追いつかないときにサーバー側のバッファが溢れないようにするバックプレッシャー制御も必要です(WebSocket.org: WebSockets at Scale)。
| 観点 | SSE | WebSocket |
|---|---|---|
| ロードバランサ | 通常の HTTP として処理 | スティッキーセッション or 外部状態ストアが要る |
| 複数サーバー配信 | バックエンドの Pub/Sub で素直 | Pub/Sub バックプレーン必須 |
| 障害時の復旧 | ブラウザ自動再接続 + Last-Event-ID | 再接続・状態復元を自前設計 |
| CDN/プロキシ | 乗せやすい | 専用設定・対応が必要 |
つまり同じ「リアルタイム機能」でも、SSE で済む要件を WebSocket で作ると、本番でかかる運用コスト(インフラ・監視・障害対応)が桁違いに膨らみます。設計段階でこの差を織り込めるかどうかが、後から効いてくる分岐点です。
内製と外注を分ける判断軸
リアルタイム機能を内製するか外注するかは、「動くデモ」ではなく「本番運用に乗せた状態」で見積もれるかどうかで決まります。前述のとおり、WebSocket の再接続・認証・順序保証・水平スケール・バックプレッシャー、SSE の HTTP/2 前提とプロキシのバッファリング設定といった論点は、いずれもデモには現れず本番でだけ牙をむきます。
自社に WebSocket の水平スケールや SSE のインフラ設定を運用した経験があるチームがいるなら内製が合理的です。逆に「チャットや共同編集を企画しているが、再接続が切れる・特定ユーザーだけ通知が来ない・同時接続が増えると落ちる、といった本番障害を経験したことがない」状態であれば、設計の段階から実装経験のある外部に委ねたほうが、結果的にコストとリスクを抑えられます。判断のポイントは次の3つです。
- 要件の双方向性が確定しているか:通知中心なら SSE で薄く作れる。チャット・共同編集が絡むなら WebSocket のスケール設計まで含めて見積もる必要がある
- 本番のピーク同時接続数を見積もれているか:同時接続が増えたときの挙動(接続枠・バックプレーン・状態復元)を設計に織り込めているか
- 障害時の復旧フローを設計できるか:再接続・取りこぼし補完・監視を、機能リリースと同時に用意できるか
リアルタイム機能は「画面で動いて見える」ため軽く見られがちですが、本番運用の品質を左右するのは表に出ないこれらの設計です。企画段階で技術選定とスケール設計を一体で詰めておくことが、後戻りの少ないリアルタイム実装につながります。
この記事を書いた人

コセケン
テクラル合同会社
スタートアップでのCTO経験を経て、現在はテクラル合同会社にてシステム開発全般を牽引しています。アプリおよびWebの開発から、バックエンド、インフラ構築に至るまで幅広い技術領域に対応可能です。スピード感を持った品質の高いシステム開発を得意としており、新規プロダクトの立ち上げを一気通貫で支援します。本ブログでは実践的な開発ノウハウを発信していきます。


