DEV Community

Ahmed Mahmoud
Ahmed Mahmoud

Posted on • Originally published at devya.dev

WebSockets on Vercel Functions: Real-Time Without a Separate Server

Headline: Vercel Functions can now hold WebSocket connections open directly on Fluid Compute, the same runtime that already serves my regular API routes — I stopped standing up a dedicated Socket.IO server and a Redis pub/sub layer just to push live updates to a browser tab.

Key takeaways

  • Vercel Functions support WebSockets natively through experimental_upgradeWebSocket() from @vercel/functions, running on Fluid Compute — no standalone WebSocket server or third-party realtime service required.
  • Fluid Compute reuses a warm function instance across concurrent requests instead of spinning up one instance per request; that's what makes holding a long-lived socket open practical on a platform historically built around short request/response cycles.
  • Standard Node.js libraries like ws work inside a Vercel Function unchanged — I didn't rewrite client or server socket logic to move this off a dedicated server.
  • WebSockets still win over Server-Sent Events for anything bidirectional: chat input, collaborative cursors, live multiplayer state. SSE stays the simpler choice for one-way streaming.
  • Managed services like Pusher or Ably still earn their place at large fan-out scale or when you need built-in presence and room primitives; for a single app's realtime feature, self-hosting on Vercel Functions is now a realistic default.

Can a Vercel Function actually keep a WebSocket connection open?

Yes, as long as it runs on Fluid Compute, the default runtime for Vercel Functions. The old serverless mental model — one isolated container per invocation, torn down the moment the response finishes — never had a slot for a connection that stays open for minutes or hours.

Fluid Compute changes the unit of work: instances are reused across concurrent requests, so a function can keep a socket alive across that reused instance instead of dying the second it returns a response. That's also why the platform supports graceful shutdown and request cancellation on the same runtime — a function mid-stream when a deploy rolls out gets a chance to close connections cleanly instead of being killed mid-write.

For a chat feature or a live dashboard, that's the difference between a socket that silently drops on every deploy and one that reconnects predictably.

How do I upgrade a Vercel Function to a WebSocket connection?

I call experimental_upgradeWebSocket() from @vercel/functions inside a route handler, the same way I'd upgrade an HTTP connection on any Node.js server:

// app/api/live/route.ts
import { experimental_upgradeWebSocket } from '@vercel/functions';

export function GET(request: Request) {
  return experimental_upgradeWebSocket(request, {
    onOpen(ws) {
      ws.send(JSON.stringify({ type: 'connected' }));
    },
    onMessage(ws, event) {
      const payload = JSON.parse(event.data as string);
      ws.send(JSON.stringify({ type: 'ack', received: payload }));
    },
    onClose(ws, event) {
      // clean up per-connection state
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

On the client it's a plain WebSocket — nothing Vercel-specific to install:

const socket = new WebSocket('wss://your-app.vercel.app/api/live');
socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
};
Enter fullscreen mode Exit fullscreen mode

If I'm already using ws for connection pooling, rooms, or heartbeat handling, that code carries over — the upgrade point is the only thing that's Vercel-specific.

When should I reach for WebSockets instead of SSE or polling?

The deciding question is direction: does the client need to send frequent messages back, not just receive them? If yes, WebSockets are the right tool. If the client only ever listens, Server-Sent Events are simpler to operate and debug.

Concern WebSocket SSE Polling
Direction Bidirectional Server → client only Client-initiated, repeated
Protocol Own upgrade (ws://, wss://) Plain HTTP, text/event-stream Plain HTTP requests
Reconnection Manual Built into EventSource N/A
Good fit Chat, collaborative editing, multiplayer cursors Token streaming, notifications, live logs Low-frequency status checks
Infra on Vercel experimental_upgradeWebSocket() + Fluid Compute Any Node.js runtime None

I'd already covered SSE for token streaming in an earlier post — that's still the right call for one-way AI output. WebSockets are for the features where the browser talks back.

Do I still need Pusher, Ably, or a standalone WebSocket server?

Sometimes, but not by default anymore. Managed realtime platforms like Pusher and Ably handle two things that are genuinely hard to build yourself: fan-out across thousands of concurrent connections, and presence/room primitives as an out-of-the-box API. If your feature is at that scale, a managed service is still the pragmatic choice.

What changed is the baseline. Before, any WebSocket on Vercel meant running a separate always-on server elsewhere just to hold the socket. Now a single-feature realtime need can live entirely inside the same Vercel Function that serves the rest of your API. One deploy target, one auth model, one set of environment variables.

What connection limits and reconnection patterns do I need to handle myself?

WebSockets don't get automatic reconnection the way EventSource does for SSE — that logic is on you.

  • Exponential backoff on reconnect — retry with increasing delay (1s, 2s, 4s, capped around 30s) instead of hammering the endpoint immediately.
  • Heartbeat pings — send a ping on an interval, expect a pong back; if none arrives, treat the connection as dead and reconnect.
  • Idle timeouts are real — a connection with no traffic can be dropped by intermediate proxies; the heartbeat above doubles as keep-alive.
  • Resync state on reconnect — design the protocol so the server can answer "catch me up from X" rather than assuming the client saw every message.

None of this is Vercel-specific — it's the same discipline any WebSocket client needs — but it matters more here because there's no managed service hiding it from you.

FAQ

Q: Do I need the Edge runtime to use WebSockets on Vercel?

A: No. WebSocket support runs on Fluid Compute with the standard Node.js runtime — you don't need runtime = 'edge'.

Q: Can I use the ws npm package directly instead of experimental_upgradeWebSocket()?

A: experimental_upgradeWebSocket() performs the HTTP-to-WebSocket upgrade inside a Vercel Function; once open, existing socket-handling code, including logic built around ws, carries over largely unchanged.

Q: Is this the same as SSE for AI streaming?

A: No. SSE is one-way and remains the right choice for streaming AI tokens. WebSockets are bidirectional and fit features where the client also sends frequent messages back.

Q: Do WebSocket connections survive a new deployment?

A: Fluid Compute supports graceful shutdown and request cancellation, giving in-flight connections a chance to close cleanly during a deploy — but your client should still implement reconnect logic for the brief rollover window.

Q: Do I still need Redis for multi-instance fan-out?

A: If your app runs on a single reused Fluid Compute instance, in-memory broadcast is enough. Once traffic spans multiple instances, a shared layer like Redis pub/sub is still needed to fan messages out across them.


Originally published on devya.dev. Also on eng-ahmed.com. Built by Devya Solutions.

Top comments (0)