PushFlo
Back to Blog
7 min readBy Marek

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.

next.jsreal-timevercelwebsockettutorial

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:

  1. Serverless functions are stateless — They spin up, execute, and terminate. There's no way to maintain a persistent WebSocket connection.

  2. Vercel has execution limits — Functions timeout after 10-60 seconds depending on your plan. WebSocket connections need to stay open indefinitely.

  3. 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:

  1. Use specific channels — Don't broadcast everything to everyone. Use user-specific channels (user-123) or resource-specific channels (order-456).

  2. Keep payloads small — The 32KB limit is generous, but smaller messages = lower latency. Send IDs and let clients fetch full data if needed.

  3. 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:

ApproachMonthly CostComplexity
Polling every 5 seconds~$200+ in API callsLow
Self-hosted WebSocket server$50-200+ (server costs)High
Enterprise services (Pusher)$49-299/monthMedium
PushFlo$0-19/monthLow

For most indie projects, the free tier is more than enough to get started and validate your idea.

Common Pitfalls to Avoid

  1. Don't subscribe on every render — Use useEffect with proper dependencies and cleanup.

  2. Don't forget authentication — For sensitive data, validate user permissions before publishing to user-specific channels.

  3. 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.