Client-Side Usage
The useRealtime hook connects your React components to realtime events with full type safety.
Setup
1. Add the Provider
Wrap your app in the RealtimeProvider:
"use client"import { RealtimeProvider } from "@upstash/realtime/client"export function Providers({ children }: { children: React.ReactNode }) { return <RealtimeProvider>{children}</RealtimeProvider>}import { Providers } from "./providers"export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <Providers>{children}</Providers> </body> </html> )}2. Create Typed Hook
Create a typed useRealtime hook using createRealtime:
"use client"import { createRealtime } from "@upstash/realtime/client"import type { RealtimeEvents } from "./realtime"export const { useRealtime } = createRealtime<RealtimeEvents>()Basic Usage
Subscribe to events in any client component:
"use client"import { useRealtime } from "@/lib/realtime-client"export default function Page() { useRealtime({ events: ["notification.alert"], onData({ event, data, channel }) { console.log(`Received ${event}:`, data) }, }) return <p>Listening for events...</p>}Provider Options
API configuration: - url: The realtime endpoint URL - withCredentials: Whether to
send cookies with requests
Maximum number of reconnection attempts before giving up
"use client"import { RealtimeProvider } from "@upstash/realtime/client"export function Providers({ children }: { children: React.ReactNode }) { return ( <RealtimeProvider api={{ url: "/api/realtime", withCredentials: true }} maxReconnectAttempts={5} > {children} </RealtimeProvider> )}Hook Options
Array of event names to subscribe to (e.g. ["notification.alert", "chat.message"])
Callback when an event is received. Receives an object with event, data, and
channel.
Array of channel names to subscribe to
Whether the subscription is active. Set to false to disconnect.
Return Value
The hook returns an object with:
Current connection state: "connecting", "connected", "disconnected", or "error"
import { useRealtime } from "@/lib/realtime-client"const { status } = useRealtime({ events: ["notification.alert"], onData({ event, data, channel }) {},})console.log(status)Connection Control
Enable or disable connections dynamically:
"use client"import { useState } from "react"import { useRealtime } from "@/lib/realtime-client"export default function Page() { const [enabled, setEnabled] = useState(true) const { status } = useRealtime({ enabled, events: ["notification.alert"], onData({ event, data, channel }) { console.log(event, data, channel) }, }) return ( <div> <button onClick={() => setEnabled((prev) => !prev)}> {enabled ? "Disconnect" : "Connect"} </button> <p>Status: {status}</p> </div> )}Conditional Connections
Connect only when certain conditions are met:
"use client"import { useRealtime } from "@/lib/realtime-client"import { useUser } from "@/hooks/auth"export default function Page() { const { user } = useUser() useRealtime({ enabled: Boolean(user), channels: [`user-${user?.id}`], events: ["notification.alert"], onData({ event, data, channel }) { console.log(data) }, }) return <p>Notifications {user ? "enabled" : "disabled"}</p>}Multiple Events
Subscribe to multiple events at once:
"use client"import { useRealtime } from "@/lib/realtime-client"export default function Page() { useRealtime({ events: ["chat.message", "chat.reaction", "user.joined"], onData({ event, data, channel }) { // 👇 data is automatically typed based on the event if (event === "chat.message") console.log("New message:", data) if (event === "chat.reaction") console.log("New reaction:", data) if (event === "user.joined") console.log("User joined:", data) }, }) return <p>Listening to multiple events</p>}Multiple Channels
Subscribe to multiple channels at once:
"use client"import { useRealtime } from "@/lib/realtime-client"export default function Page() { useRealtime({ channels: ["global", "announcements", "user-123"], events: ["notification.alert"], onData({ event, data, channel }) { console.log(`Message from ${channel}:`, data) }, }) return <p>Listening to multiple channels</p>}Dynamic Channel Management
Add and remove channels dynamically:
"use client"import { useState } from "react"import { useRealtime } from "@/lib/realtime-client"export default function Page() { const [channels, setChannels] = useState<string[]>(["lobby"]) useRealtime({ channels, events: ["chat.message"], onData({ event, data, channel }) { console.log(`Message from ${channel}:`, data) }, }) const joinRoom = (roomId: string) => { setChannels((prev) => [...prev, roomId]) } const leaveRoom = (roomId: string) => { setChannels((prev) => prev.filter((c) => c !== roomId)) } return ( <div> <p>Active channels: {channels.join(", ")}</p> <button onClick={() => joinRoom("room-1")}>Join Room 1</button> <button onClick={() => joinRoom("room-2")}>Join Room 2</button> <button onClick={() => leaveRoom("lobby")}>Leave Lobby</button> </div> )}Custom API Endpoint
Configure a custom realtime endpoint in the provider:
"use client"import { RealtimeProvider } from "@upstash/realtime/client"export function Providers({ children }: { children: React.ReactNode }) { return ( <RealtimeProvider api={{ url: "/api/custom-realtime", withCredentials: true, }} > {children} </RealtimeProvider> )}Use Cases
Live Notifications
Show real-time notifications to users:
"use client"import { useRealtime } from "@/lib/realtime-client"import { toast } from "react-hot-toast"import { useUser } from "@/hooks/auth"export default function Notifications() { const { user } = useUser() useRealtime({ channels: [`user-${user.id}`], events: ["notification.alert"], onData({ data }) { toast(data) }, }) return <p>Listening for notifications...</p>}Realtime Chat
Build a real-time chat:
"use client"import { useState } from "react"import { useRealtime } from "@/lib/realtime-client"import z from "zod/v4"import type { RealtimeEvents } from "@/lib/realtime"type Message = z.infer<RealtimeEvents["chat"]["message"]>export default function Chat() { const [messages, setMessages] = useState<Message[]>([]) useRealtime({ channels: ["room-123"], events: ["chat.message"], onData({ data }) { setMessages((prev) => [...prev, data]) }, }) return ( <div> {messages.map((msg, i) => ( <p key={i}> <span className="font-bold">{msg.sender}:</span> {msg.text} </p> ))} </div> )}Live Dashboard
Update metrics in real-time:
"use client"import { useQuery, useQueryClient } from "@tanstack/react-query"import { useRealtime } from "@/lib/realtime-client"export default function Dashboard() { const queryClient = useQueryClient() const { data: metrics } = useQuery({ queryKey: ["metrics"], queryFn: async () => { const res = await fetch("/api/metrics?user=user-123") return res.json() }, }) useRealtime({ channels: ["user-123"], events: ["metrics.update"], onData() { queryClient.invalidateQueries({ queryKey: ["metrics"] }) }, }) return ( <div> <p>Active Users: {metrics?.users}</p> <p>Revenue: ${metrics?.revenue}</p> </div> )}Collaborative Editing
Sync changes across users:
"use client"import { useState } from "react"import { useRealtime } from "@/lib/realtime-client"export default function Editor({ documentId }: { documentId: string }) { const [content, setContent] = useState("") useRealtime({ channels: [`doc-${documentId}`], events: ["document.update"], onData({ data }) { setContent(data.content) }, }) return <textarea value={content} onChange={(e) => setContent(e.target.value)} />}