How to Add Real-time Features to Next.js Without Managing Servers
Learn how to add WebSocket-powered real-time features like live notifications, chat, and dashboards to your Next.js app deployed on Vercel — without running your own WebSocket server.
If you've ever tried to add real-time features to a Next.js app deployed on Vercel, you've probably hit a wall. WebSockets don't work with serverless functions. Vercel's own documentation confirms it: "Serverless Functions do not support WebSocket connections."
But your users expect real-time. They want to see notifications appear instantly, dashboards update without refreshing, and messages arrive the moment they're sent.
In this guide, I'll show you how to add real-time features to your Next.js app without spinning up a WebSocket server, without leaving Vercel, and without the complexity of enterprise solutions.
The Problem: Next.js + Vercel + WebSockets Don't Mix
Here's why real-time is hard on serverless:
-
Serverless functions are stateless — They spin up, execute, and terminate. There's no way to maintain a persistent WebSocket connection.
-
Vercel has execution limits — Functions timeout after 10-60 seconds depending on your plan. WebSocket connections need to stay open indefinitely.
-
No shared state — Each function invocation is isolated. You can't broadcast a message to "all connected users" because there's no concept of connected users at the function level.
The traditional solutions are:
- Polling — Wasteful, high latency, expensive at scale
- Self-hosted WebSocket server — Defeats the purpose of serverless
- Enterprise services — Overkill pricing for indie developers (see our Pusher and Ably comparisons)
There's a better way.
The Solution: Let Someone Else Hold the Connections
The key insight is this: you don't need to host WebSocket connections yourself. You just need to send messages to connected users.
This is exactly what PushFlo does:
- PushFlo maintains WebSocket connections with your users
- Your Next.js API routes send messages via simple HTTP POST requests
- Users receive updates instantly through their WebSocket connection
Your serverless functions stay serverless. Your users get real-time updates. Everyone wins.
Setting Up Real-time in Next.js (Step by Step)
Let's build a real-time notification system. When something happens in your app (new order, new message, status change), users will see it instantly.
Step 1: Install the SDK
npm install @pushflodev/sdk
Step 2: Create a PushFlo Account
Head to console.pushflo.dev and create a free account. You'll get:
- 500,000 messages/month
- 50 concurrent connections
- No credit card required
Grab your API key from the dashboard.
Step 3: Set Up Environment Variables
Create or update your .env.local:
PUSHFLO_API_KEY=your_api_key_here
NEXT_PUBLIC_PUSHFLO_APP_ID=your_app_id_here
Step 4: Create a Client-Side Hook
Create a reusable hook for subscribing to real-time updates:
// hooks/usePushFlo.ts
import { useEffect, useState } from 'react';
import { PushFloClient } from '@pushflodev/sdk';
export function usePushFlo(channel: string) {
const [messages, setMessages] = useState<any[]>([]);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const client = new PushFloClient({
publishKey: process.env.NEXT_PUBLIC_PUSHFLO_APP_ID!,
});
client.connect();
client.on('connected', () => {
setIsConnected(true);
});
client.subscribe(channel, {
onMessage: (message) => {
setMessages((prev) => [...prev, message]);
},
});
return () => {
client.disconnect();
};
}, [channel]);
return { messages, isConnected };
}
Step 5: Use the Hook in Your Components
// components/NotificationBell.tsx
'use client';
import { usePushFlo } from '@/hooks/usePushFlo';
export function NotificationBell({ userId }: { userId: string }) {
const { messages, isConnected } = usePushFlo(`user-${userId}`);
const unreadCount = messages.filter(m => !m.read).length;
return (
<div className="relative">
<BellIcon className="h-6 w-6" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
{unreadCount}
</span>
)}
{isConnected && <span className="absolute bottom-0 right-0 h-2 w-2 bg-green-500 rounded-full" />}
</div>
);
}
Step 6: Send Messages from Your API Routes
When something happens that should trigger a notification, send a message from your API route:
// app/api/orders/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const order = await request.json();
// Save the order to your database
const savedOrder = await db.orders.create(order);
// Send real-time notification to the user
await fetch('https://api.pushflo.dev/api/v1/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.PUSHFLO_API_KEY}`,
},
body: JSON.stringify({
channel: `user-${order.userId}`,
event: 'new-order',
data: {
orderId: savedOrder.id,
message: 'Your order has been placed!',
timestamp: new Date().toISOString(),
},
}),
});
return NextResponse.json(savedOrder);
}
That's it. When an order is created, the user sees a notification instantly — no page refresh, no polling, no WebSocket server to manage.
Want a complete notification system? Check out Building Live Notifications in Under 10 Minutes for a step-by-step tutorial with a notification bell, toast popups, and server-side triggers.
Real-World Use Cases
This pattern works for any real-time feature:
Live Dashboard Updates
// When metrics change
await pushflo.publish({
channel: 'dashboard-metrics',
event: 'update',
data: { activeUsers: 1234, revenue: 56789 }
});
Chat Messages
// When a message is sent
await pushflo.publish({
channel: `chat-${roomId}`,
event: 'message',
data: { sender: userId, text: messageText }
});
Collaborative Editing Presence
// When a user starts editing
await pushflo.publish({
channel: `doc-${documentId}`,
event: 'presence',
data: { userId, cursor: { x: 100, y: 200 } }
});
Order Status Tracking
// When order status changes
await pushflo.publish({
channel: `order-${orderId}`,
event: 'status-update',
data: { status: 'shipped', trackingNumber: 'ABC123' }
});
Handling Missed Messages
What if a user's connection drops and they miss messages? PushFlo includes message history:
const client = new PushFloClient({
publishKey: process.env.NEXT_PUBLIC_PUSHFLO_APP_ID!,
// Replay messages from the last hour on reconnect
replayOnReconnect: true,
});
The free tier keeps 1 day of history; Starter plan extends this to 3 days.
Performance Considerations
A few tips for production:
-
Use specific channels — Don't broadcast everything to everyone. Use user-specific channels (
user-123) or resource-specific channels (order-456). -
Keep payloads small — The 32KB limit is generous, but smaller messages = lower latency. Send IDs and let clients fetch full data if needed.
-
Debounce rapid updates — If you're sending many updates per second (like cursor positions), consider batching or throttling.
Cost Comparison
Let's compare the cost of different approaches for 10,000 daily active users:
| Approach | Monthly Cost | Complexity |
|---|---|---|
| Polling every 5 seconds | ~$200+ in API calls | Low |
| Self-hosted WebSocket server | $50-200+ (server costs) | High |
| Enterprise services (Pusher) | $49-299/month | Medium |
| PushFlo | $0-19/month | Low |
For most indie projects, the free tier is more than enough to get started and validate your idea.
Common Pitfalls to Avoid
-
Don't subscribe on every render — Use
useEffectwith proper dependencies and cleanup. -
Don't forget authentication — For sensitive data, validate user permissions before publishing to user-specific channels.
-
Don't ignore reconnection — The SDK handles this automatically, but make sure your UI reflects connection state.
Conclusion
Real-time features shouldn't require a PhD in distributed systems. With Next.js, Vercel, and PushFlo, you can:
- Keep your serverless architecture
- Add instant notifications, live updates, and real-time collaboration
- Deploy everything to Vercel with zero infrastructure to manage
- Start for free and scale as you grow
The pattern is simple: PushFlo holds the connections, you send the messages. Your users get the real-time experience they expect. For more details, see our guide on WebSockets for Vercel.
Ready to add real-time to your Next.js app? Get started with PushFlo for free — no credit card required.
Add real-time to your Next.js app in minutes
PushFlo works natively with Vercel — no servers, no config, just real-time.
Related Articles
Do You Really Need Millions of Messages? The Real-time Pricing Trap
Why most real-time services sell you quotas you'll never use. A honest look at what indie developers actually need from WebSocket infrastructure.
WebSocket Alternatives for Vercel and Cloudflare Workers
Vercel and Cloudflare Workers don't support WebSockets natively. Here are your options for adding real-time features to serverless apps, from polling to managed services.
Polling vs WebSockets: Cost Comparison for Serverless Apps
A detailed breakdown of the true costs of polling vs WebSocket-based real-time features in serverless applications. Includes calculations for Vercel, AWS Lambda, and managed WebSocket services.
