The easiest way to add realtime features to any Next.js project.
- Setup takes 60 seconds
- Clean APIs & first-class TypeScript support
- Extremely fast, zero dependencies, 1.9kB gzipped
- Deploy anywhere: Vercel, Netlify, etc.
- 100% type-safe using zod v4 or zod mini
- Automatic connection management w/ message delivery guarantee
- Built-in middleware and helpers - batteries included
- HTTP-based: Redis streams & server-sent events
bun install @upstash/realtime
Upstash realtime is powered by Redis. We'll assume you already have the @upstash/redis
package installed:
// lib/redis.ts
import { Redis } from "@upstash/redis"
export const redis = new Redis({
// 👇 grab these from the Upstash Redis dashboard
url: "https://striking-osprey-20681.upstash.io",
token: "AVDJAAIjcDEyZ...",
})
Define the structure of realtime events in your app.
// lib/realtime.ts
import { Realtime, InferRealtimeEvents } from "@upstash/realtime"
import { redis } from "./redis"
import z from "zod"
// 👇 later triggered with `realtime.notification.alert.emit()`
const schema = {
notification: z.object({
alert: z.string(),
}),
}
export const realtime = new Realtime({ schema, redis })
export type RealtimeEvents = InferRealtimeEvents<typeof realtime>
Create your realtime endpoint under /api/realtime/route.ts
. It's important that your route matches this path exactly.
// api/realtime/route.ts
import { handle } from "@upstash/realtime"
import { realtime } from "@/lib"
export const GET = handle({ realtime })
await realtime.notification.alert.emit("Hello world")
"use client"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
export default function MyComponent() {
useRealtime<RealtimeEvents>({
events: {
notification: {
// 👇 100% type-safe
alert: (data) => console.log(data),
},
},
})
return <div>Listening for events...</div>
}
Serverless functions have execution time limits. The client automatically reconnects before timeout, with Redis Streams guaranteeing that no message is lost.
If you deploy to Vercel: Make sure fluid compute is enabled for your project. This allows very high timeout limits, even for free users. Also, your functions will not be billed based on open time, but on active CPU time (only while realtime messages are processed).
export const realtime = new Realtime({
schema,
redis,
// 👇 Set to - Vercel free plan: 300; Vercel pro plan: 800
maxDurationSecs: 300,
})
Match this to your API route timeout:
// api/realtime/route.ts
// 👇 Set to - Vercel free plan: 300; Vercel pro plan: 800
export const maxDuration = 300
export const GET = handle({ realtime })
Vercel Note: With fluid compute (default), you're only billed for active CPU time, not connection duration. This makes Upstash Realtime very cost-efficient.
Scope events to channels or rooms:
await realtime.channel("room-123").notification.alert.emit("Hello")
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
useRealtime<RealtimeEvents>({
channel: "room-123",
events: {
notification: {
alert: (data) => console.log(data),
},
},
})
import { useState } from "react"
import { useRealtime } from "@upstash/realtime/client"
import type { RealtimeEvents } from "@/lib/realtime"
const [enabled, setEnabled] = useState(true)
const { status } = useRealtime<RealtimeEvents>({
enabled,
events: {
notification: {
alert: (data) => console.log(data),
},
},
})
Protect your realtime endpoints with custom authentication logic.
import { realtime } from "@/lib"
import { handle } from "@upstash/realtime"
import { currentUser } from "@/auth"
export const GET = handle({
realtime,
middleware: async ({ request, channel }) => {
const user = await currentUser(request)
if (channel === user.id) {
return
}
if (channel !== user.id) {
return new Response("Unauthorized", { status: 401 })
}
},
})
The middleware function receives:
request
: The incoming Request objectchannel
: The channel the client is attempting to connect to
Return undefined
or nothing to allow the connection. Return a Response
object to block the connection with a custom error.
MIT