I tried out Vercel Functions now that they support WebSockets

I tried out Vercel Functions now that they support WebSockets

Vercel Functions added the ability to natively accept WebSocket connections as a public beta in June 2026. When actually deployed and verified, the connection was disconnected after approximately 5 minutes and 10 seconds without a clean termination.
2026.08.14

This page has been translated by machine translation. View original

Introduction

On June 22, 2026, Vercel announced native WebSocket connection support as a public beta.

"Native" here means that WebSocket Upgrade requests are accepted within the Function's maximum execution time, and a single connection is pinned to a single Function instance for processing. In a previous article, we introduced Vercel as not supporting WebSockets. This article examines what has changed and verifies it by actually deploying a WebSocket server to Vercel.

Specifically, we verify the two points explained in the official documentation—"bidirectional communication is possible" and "connections are terminated at the default Function execution time (5 minutes)"—by actually connecting and checking. In practice, the connection was severed at approximately 5 minutes and 10 seconds, and the disconnection was not a normal closure but was detected as a communication error. However, reconnection succeeds immediately.

What Is Vercel Functions' WebSocket Support?

A WebSocket connection begins as an HTTP GET request with an Upgrade header. Vercel processes this through the same path as regular requests—Routing Middleware, rewrites, Firewall rules, and so on—before upgrading it to WebSocket. Once upgraded, the connection is pinned to the same Function instance for the duration of that connection. Thanks to Fluid Compute, a single instance can handle multiple WebSocket connections simultaneously.

A wide range of frameworks are supported, including plain Node.js + ws, server frameworks such as Express, Hono, and h3, Socket.IO, the Bun runtime's native Bun.serve(), Nitro, and Python.

Test Environment

  • Node.js runtime: v24.18.0
  • Test library: ws (both server and client)

Target Audience

  • Those considering real-time features using WebSockets on Vercel
  • Those who previously chose an external real-time infrastructure because "Vercel does not support WebSockets"
  • Those who want actual measurement data on how many seconds it takes for a connection to drop and how it drops

References

Test Method

A minimal echo server was deployed to Vercel. The configuration simply exports a plain http.createServer and WebSocketServer from api/ws.js.

Received JSON messages are sent back as-is. Elapsed time since connection start is measured using the monotonic clock process.hrtime.bigint() inside the server. Each occurrence of open, message, close, and error is recorded via console.log.

Logs can be retrieved with vercel logs --json.

api/ws.js (excerpt)
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() });
  });
});

The client likewise measures elapsed time using a monotonic clock via performance.now(). The approach of cross-referencing wall clocks between hosts was not used, as it is subject to NTP synchronization drift and log ingestion delays.

The client performs the following three steps in order:

  1. Connect, exchange one round-trip message, and confirm basic communication
  2. Keep the connection open, send a Ping control frame ws.ping() every 20 seconds, and record Pong receipts. As a safety measure, close the connection after a maximum of 400 seconds
  3. After the connection in step 2 ends, attempt reconnection with exponential backoff and confirm that echo messages go through
client.js (excerpt)
pingTimer = setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.ping();
  }
}, 20000);

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

Test Results: Basic Bidirectional Communication

We connected to the echo server and confirmed the round trip from sending a message to receiving a response.

The connection, sending, and response all succeeded without issues. The server-side logs also recorded the same exchange.

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

Bidirectional message sending and receiving worked as described in the official documentation.

Test Results: Connection Time Limit and Reconnection

We kept the connection open while continuously sending Pings, and ran two trials to determine how many seconds it would take for the connection to terminate and in what manner.

The results were as follows:

Trial Elapsed time until disconnection [s] Close code Reconnection
1 310.4 1006 (abnormal closure, no Close frame) Succeeded on first attempt (after 1-second wait)
2 310.9 1006 (abnormal closure, no Close frame) Succeeded on first attempt (after 1-second wait)

The Function's maxDuration was 300 seconds, but the actual cutoff occurred around 310 seconds. Both trials showed a gap of about 10 seconds, indicating this is a reproducible behavior rather than a one-off error. Ping and Pong were exchanging normally right up until the moment of disconnection. The server appeared to still be in a responsive state, suggesting it was terminated from the outside.

The disconnection was observed as close code 1006 (abnormal closure). Per the WebSocket specification, this is a value generated by the client when a normal Close frame is not received. The server-side logs also contained no entries explaining the disconnection itself (no close event or error event). It appears the Function was cut off entirely without being given the opportunity to send a Close frame to the application.

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

Reconnection succeeded immediately on the first attempt (after a 1-second wait) in both trials, and echo messages returned normally. Recovery after a dropped connection poses no issues.

Discussion

The two points—that the connection drops around 5 minutes and 10 seconds rather than exactly 5 minutes, and that no Close frame is sent—are behaviors that could not be determined from the official documentation alone.

In implementations that only handle close event codes in the normal 1000-range, there is a risk of overlooking code 1006. Reconnection logic should be triggered by unexpected disconnections such as code 1006 as well.

Summary

In June 2026, Vercel Functions added the ability to natively accept WebSocket connections on Fluid Compute as a public beta. Testing with an actual deployment confirmed that bidirectional communication works without issues. Connections are terminated in accordance with the Function's execution time (default 5 minutes), but measurements showed termination occurring around 5 minutes and 10 seconds, observed as close code 1006 (termination without a Close frame). Reconnection succeeds immediately.

For those who previously chose an external real-time infrastructure because "Vercel does not support WebSocket servers," simple bidirectional connections may now be achievable with Vercel alone. However, caution is warranted as this is still a public beta. We hope the test results in this article serve as useful reference material when evaluating adoption.

Share this article