How to Implement WebSockets in Your App (2026)
WebSockets enable real-time bidirectional communication between client and server. Here's how to implement them for chat, collaboration, and live updates.
WebSockets vs Alternatives
| Technology | Best For | Latency |
|---|---|---|
| WebSockets | Chat, collaborative editing | ~5ms |
| Server-Sent Events (SSE) | Live dashboards, one-way updates | ~10ms |
| Long polling | Legacy browser support | ~500ms |
| Supabase Realtime | Database subscriptions | ~50ms |
Option 1: Socket.IO (Easiest)
Socket.IO handles reconnection, room management, and fallbacks automatically.
Server Setup (Node.js/Express)
import { createServer } from 'http'
import { Server } from 'socket.io'
import express from 'express'
const app = express()
const httpServer = createServer(app)
const io = new Server(httpServer, {
cors: { origin: process.env.CLIENT_URL },
})
io.on('connection', (socket) => {
console.log('Client connected:', socket.id)
// Join a room
socket.on('join', (roomId) => {
socket.join(roomId)
io.to(roomId).emit('user-joined', { userId: socket.id })
})
// Broadcast message to room
socket.on('message', ({ roomId, message }) => {
io.to(roomId).emit('message', { userId: socket.id, message, timestamp: Date.now() })
})
// Handle disconnect
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id)
})
})
httpServer.listen(3001)
Client Setup (React)
'use client'
import { useEffect, useState } from 'react'
import { io, Socket } from 'socket.io-client'
function ChatRoom({ roomId }: { roomId: string }) {
const [socket, setSocket] = useState<Socket | null>(null)
const [messages, setMessages] = useState<Message[]>([])
const [input, setInput] = useState('')
useEffect(() => {
const newSocket = io('http://localhost:3001')
setSocket(newSocket)
newSocket.emit('join', roomId)
newSocket.on('message', (msg) => {
setMessages(prev => [...prev, msg])
})
return () => {
newSocket.close()
}
}, [roomId])
const sendMessage = () => {
if (!socket || !input) return
socket.emit('message', { roomId, message: input })
setInput('')
}
return (
<div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.userId}:</strong> {msg.message}
</div>
))}
</div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={sendMessage}>Send</button>
</div>
)
}
Option 2: Native WebSockets (More Control)
Server (Node.js with ws)
import { WebSocketServer } from 'ws'
const wss = new WebSocketServer({ port: 3001 })
const rooms = new Map<string, Set<WebSocket>>()
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const msg = JSON.parse(data.toString())
if (msg.type === 'join') {
if (!rooms.has(msg.roomId)) rooms.set(msg.roomId, new Set())
rooms.get(msg.roomId)!.add(ws)
}
if (msg.type === 'message') {
const room = rooms.get(msg.roomId)
if (room) {
room.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'message', ...msg }))
}
})
}
}
})
ws.on('close', () => {
rooms.forEach(room => room.delete(ws))
})
})
Client
const ws = new WebSocket('ws://localhost:3001')
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'join', roomId: '123' }))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
console.log('Received:', msg)
}
ws.send(JSON.stringify({ type: 'message', roomId: '123', text: 'Hello!' }))
Option 3: Supabase Realtime (Database Subscriptions)
Perfect for syncing database changes in real-time:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(url, key)
// Subscribe to table changes
const channel = supabase
.channel('posts')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'posts',
}, (payload) => {
console.log('Change:', payload)
})
.subscribe()
Scaling WebSockets
Single server works for small apps. For scale:
1. Redis Adapter (Socket.IO)
import { createAdapter } from '@socket.io/redis-adapter'
import { createClient } from 'redis'
const pubClient = createClient({ url: 'redis://localhost:6379' })
const subClient = pubClient.duplicate()
await Promise.all([pubClient.connect(), subClient.connect()])
io.adapter(createAdapter(pubClient, subClient))
Now multiple Socket.IO servers share connection state via Redis.
2. Serverless WebSockets (AWS/Cloudflare)
Use managed WebSocket services:
- AWS API Gateway WebSockets — serverless, scales automatically
- Cloudflare Durable Objects — global, low-latency WebSockets
Authentication
// Server
io.use(async (socket, next) => {
const token = socket.handshake.auth.token
try {
const user = await verifyToken(token)
socket.data.userId = user.id
next()
} catch (err) {
next(new Error('Authentication failed'))
}
})
// Client
const socket = io('http://localhost:3001', {
auth: { token: userToken },
})
FAQ
Socket.IO vs native WebSockets?
Socket.IO for ease of use (reconnection, rooms, broadcasting). Native WebSockets for maximum control and performance.
How do I deploy WebSockets on Vercel?
You can't — Vercel doesn't support long-lived WebSocket connections. Use a separate server (Railway, Fly.io) or Supabase Realtime.
Can I use WebSockets with Next.js?
Yes, but run the WebSocket server separately. Next.js serverless functions time out after 10 seconds.
Bottom Line
Use Socket.IO for chat and collaborative features. Use Supabase Realtime for database subscriptions. Deploy WebSocket servers to Railway or Fly.io (not Vercel). Add authentication and scaling (Redis adapter) as you grow.