---
title: Publish and subscribe to realtime data on Vercel
description: Learn how to publish and subscribe to realtime data on Vercel with WebSockets, SSE, Redis, and Queues, and when a managed provider fits better.
url: /kb/guide/publish-and-subscribe-to-realtime-data-on-vercel
canonical_url: "https://vercel.com/kb/guide/publish-and-subscribe-to-realtime-data-on-vercel"
published: 2025-11-03
last_updated: 2026-08-18
authors: Vercel
related:
  - /docs/functions
  - /docs/functions/websockets
  - /docs/queues
  - /docs/global-config
  - /changelog/websocket-support-is-now-in-public-beta
  - /kb/guide/do-vercel-serverless-functions-support-websocket-connections
  - /docs/fluid-compute
  - /docs/routing-middleware
  - /docs/functions/limitations
  - /docs/functions/configuring-functions/duration
  - /kb/guide/real-time-chat-websockets
  - /kb/guide/real-time-presence-hono-react
  - /kb/guide/real-time-board-nextjs-fastapi
  - /docs/functions/streaming-functions
  - /docs/functions/usage-and-pricing
  - /docs/queues/concepts
  - /kb/guide/deploying-pusher-channels-with-vercel
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---
<!-- docsgraph:related -->
## Related pages

> **For AI agents:** Follow these links to understand how this page connects to the rest of the Vercel ecosystem. For the full cross-link map (inbound, outbound, prerequisites, and semantic neighbors), see the .graph.md link below.

- [Backends](https://vercel.com/docs/frameworks/backend?from=related) — Vercel supports a wide range of the most popular backend frameworks, optimizing how your application builds and runs no
- [Building an AI chat app with RAG and source citations on Vercel](https://vercel.com/kb/guide/building-ai-chat-app-with-rag-and-citations-on-vercel?from=related) — A production stack for AI chat with retrieval, reranking, source citations, and background ingestion on Vercel using Nex
- [Vercel vs Webflow Cloud](https://vercel.com/kb/guide/vercel-vs-webflow-cloud?from=related) — Compare Vercel and Webflow Cloud for deploying Next.js and Astro apps, including runtime, framework support, storage, pr
- [Production architecture for a RAG chatbot on Vercel](https://vercel.com/kb/guide/rag-chatbot-production-architecture-on-vercel?from=related) — Architect a production RAG chatbot on Vercel Functions with Fluid compute, AI Gateway, and a region-pinned vector store.
- [Vercel vs Railway](https://vercel.com/kb/guide/vercel-vs-railway?from=related) — A detailed guide to Vercel vs Railway: serverless vs always-on containers, container images via Dockerfile.vercel, frame
- [How can I reduce my Vercel Functions usage on Vercel?](https://vercel.com/kb/guide/how-can-i-reduce-my-serverless-execution-usage-on-vercel?from=related) — Reduce Vercel Functions usage and cost under Fluid compute pricing with caching, rendering strategies, and function conf

Full cross-link map for this page: [/kb/guide/publish-and-subscribe-to-realtime-data-on-vercel.graph.md](/kb/guide/publish-and-subscribe-to-realtime-data-on-vercel.graph.md)
<!-- /docsgraph:related -->


[Vercel Functions](https://vercel.com/docs/functions) serve [WebSocket connections](https://vercel.com/docs/functions/websockets) natively, so streaming messages to a browser takes no separate socket server. The harder part is what the connection itself can't hold. Two clients in the same chat room can land on different function instances, so the room's messages and member list have to live in a store both instances can reach.

Here's which store fits which workload, how to publish and subscribe with Redis for chat and presence, and when [Vercel Queues](https://vercel.com/docs/queues), [Global Config](https://vercel.com/docs/global-config), or a managed provider is the better fit.

## How publishing and subscribing to real-time data works on Vercel

Vercel Functions can hold a bidirectional WebSocket connection open between a client and your server-side code. Support is in [public beta](https://vercel.com/changelog/websocket-support-is-now-in-public-beta). For the support question on its own, see [WebSocket support on Vercel](https://vercel.com/kb/guide/do-vercel-serverless-functions-support-websocket-connections).

WebSockets run on [Fluid compute](https://vercel.com/docs/fluid-compute), which is enabled by default. Only projects created before April 23, 2025 may need it turned on.

A connection starts as an HTTP `GET` request with an `Upgrade` header. Before the upgrade completes, that request passes through the same controls as any other request to a function, including [Routing Middleware](https://vercel.com/docs/routing-middleware), rewrites, Firewall rules, and rate limits. After the upgrade succeeds, every message on that connection reaches the instance that accepted it.

Three properties of that model drive the rest of your design:

- **Pinning:** A connection stays on the instance that accepted it for its entire life. That instance can hold many connections at once.
  
- **Placement:** A new connection can land on any instance. After a deployment, new connections reach the new version while existing ones finish on the old one.
  
- **Duration:** A connection closes when its function hits the [maximum duration](https://vercel.com/docs/functions/limitations#max-duration). Nothing holds it open past that ceiling.
  

Fluid compute sets these generally available duration limits by plan:

| Plan       | Default | Maximum |
| ---------- | ------- | ------- |
| Hobby      | 300s    | 300s    |
| Pro        | 300s    | 800s    |
| Enterprise | 300s    | 800s    |

Those are the generally available ceilings. A separate extended maximum of 1800 seconds (30 minutes) is in beta for supported Node.js and Python runtime versions, raising the ceiling above the table rather than replacing it.

The beta carries its own conditions. [Configure function duration](https://vercel.com/docs/functions/configuring-functions/duration) for each function in code or `vercel.json`, because project-level defaults above 800 seconds are not supported. Secure Compute and Static IPs do not support durations above 800 seconds either.

Those three properties have two consequences for your design.

The first is client-side. Because a connection closes at the duration limit and reconnects elsewhere, the client needs logic to recreate the connection, resubscribe to its channels, and reload any state it needs to continue.

The second is server-side. Because no two instances share memory, rooms, presence, counters, and pub/sub coordination should be stored in an external data store. Module-level variables look correct in local development, where a single process serves every client, but break as soon as production traffic spans two instances.

## How to choose a real-time primitive for your workload

The primitive depends on what the shared store has to hold. Three questions about the data determine it:

- **Scope:** Does this state need to outlive one function instance? A message relayed to everyone in a room does, because those clients can be connected to different instances. A value computed within a single request does not.
  
- **History:** Does a client that joins late need to replay what it missed, or is live delivery enough? Chat needs both. A presence indicator needs only live delivery, because the next heartbeat arrives seconds later.
  
- **Fan-out:** How many subscribers receive each message? Ten people in a room is a routine Redis workload. Ten thousand clients on one event is a different engineering problem.
  

Common workloads map to primitives as follows:

| Workload                       | Needs durable history   | Needs cross-instance delivery  | Primitive                     |
| ------------------------------ | ----------------------- | ------------------------------ | ----------------------------- |
| Chat and messaging             | Yes                     | Yes                            | Redis Streams                 |
| Presence and rosters           | No                      | Yes                            | Redis sorted set with pub/sub |
| One-way AI or data streaming   | Resumability only       | Resumption state only          | Server-Sent Events (SSE)      |
| Backend event fan-out          | Yes                     | Yes, server side               | Vercel Queues                 |
| Feature flags and config reads | Not applicable          | No                             | Global Config                 |
| Low-frequency updates          | Not applicable          | No                             | On-demand fetch or polling    |
| High-volume client fan-out     | Depends on the workload | Yes, at high connection counts | A managed provider            |

If updates are infrequent and a few seconds of staleness is acceptable, fetch the data on demand at request time or poll for it. Polling skips the open connection and the coordination primitive underneath it. Reach for a push transport once the delay between an event and the client seeing it has to beat a poll interval.

## How to publish and subscribe to chat messages with Redis Streams

Chat has two requirements. It needs a live relay for the clients currently in the room, and a durable log for a client that joins mid-conversation and has to see recent messages.

A Redis stream covers both with one structure, so `XADD` and `XREAD` handle the relay and the history together. Pub/sub delivers live messages and keeps no log, so history would need a second structure alongside it.

Build the relay in five steps:

1. Broadcast the message to the sockets on the current instance first, so local delivery is immediate.
   
2. Write the message to the stream with `XADD`, trimming with `MAXLEN ~ 200` to cap the log at roughly 200 entries.
   
3. Tag each entry with the originating instance ID, so a reader skips entries it already delivered locally.
   
4. Run one blocking `XREAD` per instance on a duplicated connection, which wakes as soon as a new entry lands.
   
5. Seed the cursor from the stream tail with `XREVRANGE` on startup, so a fresh instance relays only entries created after it came online.
   

Skip the last step and a newly started instance replays the whole stream to every client that connects to it, duplicating messages those clients already have. History replay is a separate path that runs on join, sending the newest 50 messages to the joining socket alone.

A blocking read holds its connection until an entry arrives, so each instance keeps two connections open against Redis. Check that against your provider's connection cap.

With no `REDIS_URL` configured, the client returns `null` and messages fall back to a local broadcast. There's no history and no cross-instance delivery in that mode, so it suits local development rather than deployment.

The [real-time chat guide](https://vercel.com/kb/guide/real-time-chat-websockets) has the full implementation, including the client and the typing indicator.

## How to publish presence updates with Redis pub/sub

Presence inverts the chat tradeoff. Nobody replays who was online three minutes ago, so the durable-log half of Streams carries no weight here. The [Notion-style presence guide](https://vercel.com/kb/guide/real-time-presence-hono-react) instead pairs two lighter primitives, one holding the roster and one signaling change.

Each primitive has a distinct job:

- **A sorted set per room:** The key is `presence:online:<roomId>`, the member is the browser ID, and the score is a last-seen timestamp. Reading members prunes anything older than the 30-second stale window, then returns the live roster.
  
- **A pub/sub channel per room:** On join or leave, the instance publishes to `presence:changed:<roomId>`. Every instance pattern-subscribes and re-reads only the room that changed, which removes polling from ordinary joins and leaves.
  

A heartbeat every 5 seconds refreshes the score of each locally connected ID, and that timer is what makes presence self-healing. Clean closes happen, but you can't rely on them, because a laptop sleeps, a network drops, or an instance gets recycled. A connection that stops refreshing its timestamp falls out of the room on its own.

One detail prevents a visible bug. Track local IDs with reference counts rather than a plain set, because a tab reload briefly holds two sockets under the same identity. Without the count, the first socket closing removes a user who is still connected, and the roster flickers on every refresh. For the same coordination applied to cursor positions, see [Figma-style multiplayer cursors](https://vercel.com/kb/guide/real-time-board-nextjs-fastapi).

## How to stream real-time data one way with SSE

Bidirectional transport costs more than many features need. Most server-to-client streaming moves in one direction, and SSE fits that shape. SSE stays on standard HTTP with no upgrade handshake, and browsers reconnect on their own and send a `Last-Event-ID` header that your server can resume from.

AI SDK 5 standardized its data stream protocol on SSE for that reason, which brings keep-alive pings, reconnect handling, and standard cache behavior along with it. See [streaming from Vercel Functions](https://vercel.com/docs/functions/streaming-functions) for the response-streaming APIs.

SSE leaves one gap, which is resuming a stream that drops mid-response. The [resumable-stream library](https://github.com/vercel/resumable-stream) closes it using Redis as a pub/sub layer and a data store for stream position, and it's built for serverless environments with no sticky load balancing. When no client disconnects, the overhead is a single `INCR` and `SUBSCRIBE` per stream and everything else stays in memory.

Before you adopt it, account for what it expects from you:

- **A Redis instance:** You own provisioning, expiry configuration, and monitoring. The library sets no default stream expiry.
  
- **A persistence layer:** Something has to track which stream ID is active for each chat, as described in the [AI SDK resume-streams documentation](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams).
  
- **A** `**waitUntil**` **value:** This keeps the Redis write alive after the HTTP response closes.
  

[Active CPU pricing](https://vercel.com/docs/functions/usage-and-pricing) ties the cost of a long-lived connection to work done rather than to connection lifetime. It bills CPU only while your code runs, and pauses during I/O. Provisioned Memory still bills for the instance lifetime, so idle time costs the memory rate rather than the CPU rate.

## How to publish and subscribe to backend events with Vercel Queues

Not every publish-and-subscribe problem ends at a browser. When the subscriber is your own backend, [Vercel Queues](https://vercel.com/docs/queues/concepts) is the first-party primitive, and it gives you durability guarantees that a Redis relay doesn't.

The model differs from Redis pub/sub in ways that matter:

- **Topics are durable append-only logs:** Messages persist until they're acknowledged or expire, and a new consumer group can join at any time and replay non-expired history from the beginning.
  
- **Consumer groups process independently:** One message stream fans out to every subscribed group without coordination between them.
  
- **Delivery is at-least-once:** Failed attempts retry automatically under a visibility timeout, so write consumers to be idempotent.
  
- **Ordering is best-effort:** Messages generally arrive in publication order, but retried messages have lower priority than new ones and there's no first-in, first-out guarantee. Include sequence numbers in payloads if your consumer depends on order.
  

Queues and a socket layer combine directly. A consumer processes the message and writes the result to Redis, then the WebSocket or SSE layer relays that result to connected clients. The durable work stays on a primitive built for retries, and the live delivery stays on one built for open connections.

## When a different tool fits your realtime data better

Two cases point entirely outside a Redis primitive. One needs push-based read speed rather than a relay, and the other needs delivery guarantees at connection volumes that a self-hosted relay can't meet.

### Global Config for fast, push-based reads

Global Config reads are fast enough to appear to be a pub/sub layer. The push model behind that speed rules it out for relay logic.

The read and write characteristics pull in opposite directions:

- **Reads:** The majority complete within 15ms at the 99th percentile, often under 1ms, because data is pushed to every region ahead of time and cached until a new version is published.
  
- **Writes:** Updating an item can take up to 10 seconds to propagate globally. Avoid Global Config for data that changes frequently or gets read immediately after an update.
  
- **Pattern:** Read it from Routing Middleware for feature flags, A/B variants, and IP blocking.
  
- **Constraint:** Routing Middleware on the edge runtime is held to 50ms of net CPU time on average. Next.js 16 renames the file to `proxy.ts` and runs it on Node.js only.
  

That combination suits request-time configuration reads and rules out relaying messages between clients.

### Managed providers for high-scale fan-out

Fan-out occurs when a self-hosted Redis relay encounters a structural limit. Broadcasting to subscribers spread across a fleet costs one send per connection, and in-process helpers do not close that gap. Bun's `server.publish()` shares messages only within a single function instance.

Managed providers ship reconnection logic, message ordering, delivery guarantees, and missed-message recovery as platform features. We maintain a [Pusher Channels guide](https://vercel.com/kb/guide/deploying-pusher-channels-with-vercel), an [Ably starter kit](https://vercel.com/templates/next.js/ably-nextjs-starter-kit), and [Liveblocks templates](https://vercel.com/templates/liveblocks), plus Marketplace integrations for all three.

These providers all run alongside Vercel Functions:

- [Ably](https://ably.com/)
  
- [Convex](https://www.convex.dev/)
  
- [Firebase Realtime Database](https://firebase.google.com/docs/database)
  
- [Liveblocks](https://liveblocks.io/)
  
- [PubNub](https://www.pubnub.com/)
  
- [Pusher](https://pusher.com/)
  
- [Sendbird](https://sendbird.com/)
  
- [Supabase Realtime](https://supabase.com/realtime)
  
- [TalkJS](https://talkjs.com/)
  

Each provider takes over connection management, which moves reconnection, ordering, and recovery out of your codebase and onto their platform.

## Next steps

Pick the primitive that matches your state, then build it on a project with Fluid compute enabled.

[Start a Vercel project](https://vercel.com/new) and add [Redis from the Marketplace](https://vercel.com/marketplace/redis), or [browse the realtime templates](https://vercel.com/templates/realtime-apps) for a working starting point.

## Related resources

- [WebSockets in Vercel Functions](https://vercel.com/docs/functions/websockets)
  
- [Real-time chat with WebSockets](https://vercel.com/kb/guide/real-time-chat-websockets)
  
- [Notion-style real-time presence](https://vercel.com/kb/guide/real-time-presence-hono-react)
  
- [Vercel Queues concepts](https://vercel.com/docs/queues/concepts)
  
- [Configuring function duration](https://vercel.com/docs/functions/configuring-functions/duration)
  
- [Redis on Vercel Marketplace](https://vercel.com/marketplace/redis)
  

## Frequently asked questions

### Can Vercel Functions hold a WebSocket connection open indefinitely?

No. Connections close at the function's maximum duration, so periodic disconnects are expected, not a bug to chase. The reference client pattern backs off from 1 second, doubling to a 30-second ceiling, and re-sends its join frame on open. Raising `maxDuration` lengthens the interval without removing the reconnect.

### Do I need Redis to publish and subscribe to realtime data on Vercel?

Only for delivery across function instances. Any Redis works, because the streams, sorted sets, and channels these patterns use are standard Redis rather than provider-specific. Provisioning through the Marketplace injects `REDIS_URL` for you. Without that variable, the reference implementations broadcast within a single instance only.

### Should I use SSE or WebSockets to stream AI responses?

Use SSE for a response the model streams back to one client. Interactive AI streaming is a documented WebSocket case, so the line falls where the client sends frames mid-generation rather than waiting. Adding stream resumption changes the calculation more than the transport choice does, since resumption needs Redis either way.

### How is Vercel Queues different from Redis pub/sub?

Queues adds guarantees that pub/sub has no mechanism for, including retries under a visibility timeout and deduplication through idempotency keys. Its consumers are functions Vercel invokes, or your own workers in poll mode, rather than open sockets. Reach for pub/sub when a missed signal costs nothing and low latency outweighs delivery guarantees.

### Does Active CPU pricing make long-lived realtime connections expensive?

No, though the connection isn't free either. A function that spends 100ms processing and 400ms waiting bills 100ms of Active CPU. Provisioned Memory is billed across the full instance lifetime at the memory rate, so the cost driver for idle connections is memory, and increasing memory raises the cost.