WebSocket Alternatives for Vercel and Cloudflare Workers
Vercel and Cloudflare Workers don't support WebSockets natively. Your options for real-time in serverless apps, from polling to managed services.
You chose serverless for good reasons: zero infrastructure management, automatic scaling, pay-per-use pricing, and instant global deployment. Vercel and Cloudflare Workers deliver on all of this.
Then you tried to add real-time features and discovered the catch: WebSockets don't work.
This isn't a bug - it's a fundamental limitation of the serverless model. But it doesn't mean you're stuck. In this guide, I'll walk through every option for adding real-time functionality to your Vercel or Cloudflare Workers app, with honest pros and cons for each.
Why WebSockets Don't Work on Serverless
First, let's understand the problem. WebSockets require:
- Persistent connections - A TCP connection that stays open indefinitely
- Stateful servers - The server must "remember" which clients are connected
- Bidirectional communication - Both client and server can send messages at any time
Serverless functions are the opposite:
- Request/response only - Function starts, processes request, returns response, terminates
- Stateless - No memory between invocations
- Timeout limits - Vercel functions max out at 60 seconds (Pro) or 300 seconds (Enterprise)
A WebSocket connection that needs to stay open for hours simply cannot exist in a function that terminates after seconds.
Option 1: Polling (The "Works But..." Option)
The simplest approach: have clients repeatedly ask "anything new?"
// Client-side polling
setInterval(async () => {
const response = await fetch('/api/updates');
const updates = await response.json();
if (updates.length > 0) {
handleUpdates(updates);
}
}, 5000); // Check every 5 seconds
Pros:
- Works everywhere, no special infrastructure
- Simple to implement
- No third-party dependencies
Cons:
- High latency - 5-second polling means 2.5 seconds average delay
- Wasteful - Most requests return nothing
- Expensive at scale - 10,000 users × 12 requests/minute = 7.2 million requests/hour
- Poor UX - Users notice the delay
Verdict: Acceptable for low-frequency updates (checking order status every minute), but not viable for chat, notifications, or live collaboration.
Option 2: Long Polling (Polling's Smarter Cousin)
Long polling keeps the request open until there's data to return:
// Client-side long polling
async function longPoll() {
try {
const response = await fetch('/api/updates?wait=true');
const updates = await response.json();
handleUpdates(updates);
} catch (error) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
longPoll(); // Immediately start next request
}
Pros:
- Near-instant delivery when updates occur
- More efficient than regular polling
- Still works with standard HTTP
Cons:
- Timeout issues - Serverless functions timeout; you need to return before the limit
- Connection overhead - Still creating new connections frequently
- Complex state management - Server needs to track pending requests
- Vercel limitation - Max 60-second hold means forced reconnection cycles
Verdict: Better than polling, but the timeout limits make it awkward on serverless platforms.
Option 3: Server-Sent Events (SSE)
SSE is a one-way streaming protocol. The server can push events to the client over a single HTTP connection:
// API route (Vercel Edge Function)
export const config = { runtime: 'edge' };
export default async function handler(req) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Send an event every second
for (let i = 0; i < 30; i++) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ count: i })}\n\n`)
);
await new Promise(r => setTimeout(r, 1000));
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
Pros:
- Native browser support (EventSource API)
- Works with Edge Functions (longer timeout than serverless)
- Lower overhead than WebSockets
- Automatic reconnection built into the protocol
Cons:
- One-way only - Server to client; client-to-server still needs HTTP requests
- Still has timeouts - Edge functions eventually timeout too
- No state coordination - Multiple function instances don't share connected clients
- Can't broadcast - Each connection is isolated
Verdict: Good for streaming responses (AI chat completions), but not a full WebSocket replacement. You can't easily broadcast "user X just updated the document" to all other users.
Option 4: Third-Party Pub/Sub Services
This is where managed real-time services come in. The architecture:
- Clients connect to the third-party service via WebSocket
- Your serverless functions publish messages via REST API
- The service broadcasts messages to connected clients
Comparing the Options
| Service | Free Tier | Paid Starting | Best For |
|---|---|---|---|
| PushFlo | 500K msg/mo | $19/mo | Indie devs, teams of all sizes |
| Pusher | 200K msg/day | $49/mo | Established startups |
| Ably | 6M msg/mo | $29/mo | Enterprise features |
| Firebase RTDB | Spark plan | $25/mo+ | Google ecosystem |
| Supabase Realtime | 500 connections | Usage-based | Postgres users |
For Vercel deployments specifically, see our dedicated WebSockets for Vercel guide. If you're using other serverless platforms, check out Serverless WebSockets.
PushFlo Example (Vercel)
// Client: Subscribe to updates
import { PushFloClient } from '@pushflodev/sdk';
const client = new PushFloClient({ publishKey: 'your-publish-key' });
client.connect();
client.subscribe('notifications', {
onMessage: (message) => {
showNotification(message);
},
});
// Server: Publish updates (Vercel API Route)
export async function POST(request) {
// Do your business logic...
// Then notify clients
await fetch('https://api.pushflo.dev/api/v1/publish', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PUSHFLO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: 'notifications',
event: 'new',
data: { message: 'Something happened!' }
}),
});
return Response.json({ success: true });
}
Pros:
- True real-time - Sub-50ms latency
- No infrastructure - Someone else runs the WebSocket servers
- Scales automatically - From 1 to millions of connections
- Works with any serverless platform - Vercel, Cloudflare, Netlify, AWS Lambda
Cons:
- Third-party dependency - Your real-time features depend on their uptime
- Cost at scale - High-volume apps pay more (though still cheaper than self-hosting)
Verdict: The practical choice for most serverless apps. You get real WebSocket functionality without compromising your serverless architecture.
Option 5: Cloudflare Durable Objects (Cloudflare-Specific)
If you're all-in on Cloudflare, Durable Objects offer a unique solution. They're stateful, single-threaded "actors" that can hold WebSocket connections:
// Durable Object class
export class ChatRoom {
constructor(state, env) {
this.state = state;
this.sessions = [];
}
async fetch(request) {
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader === 'websocket') {
const pair = new WebSocketPair();
this.sessions.push(pair[1]);
pair[1].accept();
pair[1].addEventListener('message', (msg) => {
this.broadcast(msg.data);
});
return new Response(null, { status: 101, webSocket: pair[0] });
}
return new Response('Expected WebSocket', { status: 400 });
}
broadcast(message) {
this.sessions.forEach(ws => ws.send(message));
}
}
Pros:
- True WebSockets - Native support within Cloudflare's edge network
- Stateful - Objects persist and maintain state
- Global distribution - Runs at the edge, close to users
Cons:
- Cloudflare lock-in - Only works on Cloudflare Workers
- Learning curve - Different programming model than traditional serverless
- Complexity - You're managing WebSocket logic yourself
- Pricing - Durable Objects have their own pricing tier
Verdict: Powerful if you're committed to Cloudflare and willing to invest in learning the model. Overkill for simple notification features.
Decision Framework: Which Option Should You Choose?
Use this flowchart:
Is sub-second latency critical?
├── No → Consider Polling or Long Polling
└── Yes ↓
Are you Cloudflare-only and okay with vendor lock-in?
├── Yes → Consider Durable Objects
└── No ↓
Do you need full control over WebSocket logic?
├── Yes → Hybrid architecture (self-hosted)
└── No ↓
Use a managed pub/sub service (PushFlo, Pusher, Ably)
For most developers building on Vercel or Cloudflare Workers, a managed pub/sub service is the sweet spot:
- Real WebSocket functionality
- Zero infrastructure to manage
- Works with your existing serverless setup
- Free tiers to get started
Quick Start with PushFlo
Here's the minimal code to add real-time to your Vercel or Cloudflare Workers app:
1. Client-side (any framework):
import { PushFloClient } from '@pushflodev/sdk';
const client = new PushFloClient({ publishKey: 'YOUR_PUBLISH_KEY' });
client.connect();
client.subscribe('my-channel', {
onMessage: (data) => {
console.log('Received:', data);
},
});
2. Server-side (Vercel API Route):
await fetch('https://api.pushflo.dev/api/v1/publish', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PUSHFLO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: 'my-channel',
data: { hello: 'world' }
}),
});
3. Server-side (Cloudflare Worker):
export default {
async fetch(request, env) {
await fetch('https://api.pushflo.dev/api/v1/publish', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.PUSHFLO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: 'my-channel',
data: { hello: 'world' }
}),
});
return new Response('Published!');
}
}
Conclusion
WebSockets and serverless are architecturally incompatible - but that doesn't mean your users have to suffer with slow, polling-based experiences.
The modern approach is to separate concerns:
- Your serverless functions handle business logic
- A managed service handles real-time connections
This gives you the best of both worlds: the simplicity of serverless AND the instant updates of WebSockets.
Ready to add real-time to your serverless app? Start with PushFlo's free tier - 500K messages/month, no credit card required.
Real-time without managing WebSocket servers
PushFlo holds the connections so your serverless functions don’t have to.
Written by Marek
Indie developer building PushFlo - real-time messaging for serverless apps. Every post comes from running the thing in production. More about PushFlo →
Related Articles
Polling vs WebSockets: Cost Comparison for Serverless Apps
The true cost of polling vs WebSockets in serverless apps, with worked numbers for Vercel, AWS Lambda and managed WebSocket services.
Why Serverless Functions Can't Hold WebSocket Connections (And How to Fix It)
A technical deep-dive into why WebSockets don't fit serverless architecture, and the practical ways to add real-time features to your apps.
How to Add Real-time Features to Next.js Without Managing Servers
Add WebSocket-powered live notifications, chat and dashboards to a Next.js app on Vercel, without running your own WebSocket server.
