WebSocket Real-Time Features in Next.js: Chat, Notifications, and Live Updates
Adding real-time to a Next.js app is trickier than in a traditional Node server.
Here are the patterns that work in production.
Option 1: Pusher / Ably (Managed, Easiest)
For most apps, use a managed WebSocket service:
npm install pusher pusher-js
// lib/pusher.ts
import Pusher from 'pusher'
import PusherClient from 'pusher-js'
export const pusherServer = new Pusher({
appId: process.env.PUSHER_APP_ID!,
key: process.env.NEXT_PUBLIC_PUSHER_KEY!,
secret: process.env.PUSHER_SECRET!,
cluster: process.env.PUSHER_CLUSTER!,
useTLS: true,
})
export const pusherClient = new PusherClient(
process.env.NEXT_PUBLIC_PUSHER_KEY!,
{ cluster: process.env.PUSHER_CLUSTER! }
)
Trigger from server action:
async function sendMessage(channelId: string, text: string) {
const message = await db.message.create({
data: { channelId, text, userId: session.user.id }
})
// Push to all subscribers
await pusherServer.trigger(
`channel-${channelId}`,
'new-message',
message
)
return message
}
Subscribe in client component:
'use client'
function ChatRoom({ channelId }: { channelId: string }) {
const [messages, setMessages] = useState<Message[]>([])
useEffect(() => {
const channel = pusherClient.subscribe(`channel-${channelId}`)
channel.bind('new-message', (message: Message) => {
setMessages(prev => [...prev, message])
})
return () => pusherClient.unsubscribe(`channel-${channelId}`)
}, [channelId])
return <MessageList messages={messages} />
}
Option 2: Server-Sent Events (One-Way, Built-In)
For notifications and live updates (no bidirectional needed):
// app/api/events/route.ts
export async function GET(request: Request) {
const session = await getServerSession()
if (!session) return new Response('Unauthorized', { status: 401 })
const stream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder()
const send = (data: unknown) => {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(data)}\n\n`)
)
}
// Send initial connection
send({ type: 'connected' })
// Poll for new events every 2 seconds
let lastCheck = Date.now()
const interval = setInterval(async () => {
const events = await db.notification.findMany({
where: {
userId: session.user.id,
createdAt: { gt: new Date(lastCheck) },
},
})
lastCheck = Date.now()
if (events.length > 0) send({ type: 'notifications', events })
}, 2000)
request.signal.addEventListener('abort', () => {
clearInterval(interval)
controller.close()
})
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
}
Client:
function useNotifications() {
const [notifications, setNotifications] = useState<Notification[]>([])
useEffect(() => {
const eventSource = new EventSource('/api/events')
eventSource.onmessage = (e) => {
const data = JSON.parse(e.data)
if (data.type === 'notifications') {
setNotifications(prev => [...prev, ...data.events])
}
}
return () => eventSource.close()
}, [])
return notifications
}
Option 3: Socket.io with Custom Server
For complex bidirectional communication, run Socket.io alongside Next.js:
// server.ts
import { createServer } from 'http'
import { Server } from 'socket.io'
import next from 'next'
const app = next({ dev: process.env.NODE_ENV !== 'production' })
const handler = app.getRequestHandler()
app.prepare().then(() => {
const httpServer = createServer(handler)
const io = new Server(httpServer)
io.on('connection', (socket) => {
socket.on('join-room', (roomId: string) => {
socket.join(roomId)
})
socket.on('send-message', async ({ roomId, text }) => {
const message = await saveMessage(roomId, text)
io.to(roomId).emit('new-message', message)
})
})
httpServer.listen(3000)
})
Choosing the Right Approach
| Approach | Best For | Complexity |
|---|---|---|
| Pusher/Ably | Most apps, quick setup | Low |
| SSE | Notifications, live feeds | Low |
| Socket.io | Complex bidirectional | High |
| Native WebSocket | Maximum control | High |
My recommendation: Pusher for 80% of use cases. SSE if you only need server-to-client updates.
The AI SaaS Starter Kit includes Pusher integration and SSE notification patterns pre-configured. $99 one-time.
Top comments (0)