Vercel Functions が WebSocket に対応したので試してみた

Vercel Functions が WebSocket に対応したので試してみた

Vercel Functions は 2026 年 6 月、WebSocket 接続をネイティブに受け付ける機能をパブリックベータとして追加しました。実際にデプロイして検証すると、接続は約 5 分 10 秒で、正常な終了を伴わずに切断されました。
2026.08.14

はじめに

Vercel は 2026 年 6 月 22 日、WebSocket 接続をネイティブに受け付ける機能をパブリックベータとして発表しました

ここでいう「ネイティブ」とは、Function の最大実行時間の範囲内で WebSocket の Upgrade リクエストを受け付け、1 つの接続を 1 つの Function インスタンスに固定して処理できるというものです。過去記事 では Vercel が WebSocket をサポートしないと紹介しました。本記事では、ここがどう変わったのか、実際に Vercel へ WebSocket サーバーをデプロイして確かめます。

具体的には、公式ドキュメントが説明する「双方向通信ができる」「接続は既定の Function 実行時間 (5 分) で切れる」という 2 点を、接続して確認します。実際のところ、接続は約 5 分 10 秒で切断され、切断のされ方は正常な終了ではなく、通信異常として検出される形でした。 ただし再接続はすぐに成立します。

Vercel Functions の WebSocket 対応とは

WebSocket 接続は、HTTP の GET リクエストに Upgrade ヘッダーが付いた形で始まります。Vercel はこれを、Routing Middleware や rewrites、Firewall ルールなど、通常のリクエストと同じ経路で処理したうえで WebSocket に昇格させます。昇格した接続は、その接続が続く間ずっと同じ Function インスタンスに固定されます。Fluid Compute の仕組みにより、1 つのインスタンスが複数の WebSocket 接続を同時に処理できます。

対応フレームワークは幅広く、プレーンな Node.js + ws、Express・Hono・h3 などのサーバーフレームワーク、Socket.IO、Bun ランタイムのネイティブ Bun.serve()、Nitro、Python が挙げられています。

検証環境

  • Node.js ランタイム: v24.18.0
  • 検証用ライブラリ: サーバー・クライアントとも ws

対象読者

  • Vercel で WebSocket を使ったリアルタイム機能を検討している方
  • 「Vercel は WebSocket に非対応」という理由で、これまで外部のリアルタイム基盤を選んでいた方
  • 接続が何秒でどのように切れるのか、実測のデータを知りたい方

参考

検証方法

最小構成のエコーサーバーを Vercel にデプロイしました。api/ws.js から素の http.createServerWebSocketServer をエクスポートするだけの構成です。

受信した JSON メッセージは、そのまま送り返します。接続開始からの経過時間は、サーバー内の単調時計 process.hrtime.bigint() で計測します。open・message・close・error の発生ごとに console.log へ記録します。

ログは vercel logs --json で取得できます。

api/ws.js (抜粋)
const wss = new WebSocketServer({ server });

wss.on('connection', (ws) => {
  const startedAt = process.hrtime.bigint();
  const elapsedMs = () => Number(process.hrtime.bigint() - startedAt) / 1e6;

  ws.on('close', (code, reason) => {
    log({ event: 'close', elapsedMs: elapsedMs(), code, reason: reason.toString() });
  });
});

クライアントも同様に、performance.now() による単調時計で経過時間を計測します。壁時計をホスト間で突き合わせる方法は使いませんでした。NTP 同期のずれや、ログ取り込みの遅延の影響を受けるためです。

クライアントは次の 3 つを順に実施します。

  1. 接続してメッセージを 1 往復させ、基本的な疎通を確認する
  2. 接続を保持し、20 秒おきに Ping 制御フレーム ws.ping() を送って Pong の受信を記録する。安全装置として、最大 400 秒で接続を閉じる
  3. 2 の接続が終了したら、指数バックオフで再接続を試み、エコーメッセージが通ることを確認する
client.js (抜粋)
pingTimer = setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.ping();
  }
}, 20000);

ws.on('close', (code, reason) => {
  finish({ reason: 'closed', code, closeReason: reason.toString(), elapsedMs: elapsed() });
});

検証結果: 基本的な双方向通信

エコーサーバーへ接続し、メッセージを送信してから応答が返るまでを確認しました。

接続・送信・応答は、すべて問題なく成立しました。サーバー側のログにも、同じやり取りが記録されていました。

{"event":"echo_verify_message","elapsedMs":752,"data":"{\"type\":\"echo\",...,\"serverElapsedMs\":227.6}"}

双方向のメッセージ送受信は、公式ドキュメントの記載どおり動作しました。

検証結果: 接続時間の上限と再接続

接続を保持したまま Ping を送り続け、何秒で・どのような形で接続が終了するかを 2 回試行しました。

結果は次のとおりです。

試行 切断までの経過時間 [s] クローズコード 再接続
1 310.4 1006 (異常終了、Close フレームなし) 1 回目の試行 (1 秒待機後) で成功
2 310.9 1006 (異常終了、Close フレームなし) 1 回目の試行 (1 秒待機後) で成功

Function の maxDuration は 300 秒でしたが、実際に切れたのは 310 秒前後でした。2 回とも 10 秒ほどの差があり、単発の誤差ではなく再現する挙動だとわかります。切断の直前まで、Ping と Pong は正常に往復していました。サーバーは応答できる状態のまま、外側から終了させられたとみられます。

切断はクローズコード 1006 (abnormal closure) として観測されました。これは WebSocket の仕様上、正常な Close フレームを受け取れなかったときにクライアント側が生成する値です。サーバー側のログにも、切断そのものを説明する行 (close イベントや error イベント) は残っていませんでした。アプリケーションに Close フレームを送る猶予を与えず、Function ごと打ち切られている状態だと考えられます。

{"event":"ping_sent","elapsedMs":300260}
{"event":"close","elapsedMs":310431,"code":1006,"reason":""}

再接続は、2 回とも 1 秒待機した最初の試行で即座に成功し、エコーメッセージも正常に返ってきました。接続が切れたあとの復帰自体に問題はありません。

考察

5 分ちょうどではなく 5 分 10 秒前後で切れる、かつ Close フレームを伴わない、という 2 点は、公式ドキュメントだけでは分からなかった事象です。

実装では、close イベントのコードが 1000 番台の正常終了だけを想定していると、コード 1006 を見落とす可能性があります。再接続処理は、コード 1006 のような予期しない切断も含めてトリガーする必要があるでしょう。

まとめ

Vercel Functions は 2026 年 6 月、Fluid Compute 上で WebSocket 接続をネイティブに受け付ける機能をパブリックベータとして追加しました。実際にデプロイして検証したところ、双方向通信は問題なく成立しました。接続は Function の実行時間 (既定 5 分) に応じて終了しますが、実測では 5 分 10 秒前後、クローズコード 1006 (Close フレームを伴わない終了) として観測されました。再接続はすぐに成立します。

かつて「Vercel は WebSocket サーバーに非対応」という理由で外部のリアルタイム基盤を選んでいた場合、単純な双方向接続であれば Vercel だけで完結できる可能性があります。ただし、あくまでパブリックベータなので注意が必要です。本記事の検証結果が、採用を検討する際の判断材料になれば幸いです。

この記事をシェアする

関連記事