TL;DR: WebSockets create a persistent two-way connection between browser and server. Unlike HTTP (which requires a new request for every piece of data), a WebSocket stays open — both sides can send data at any time. Use WebSockets for chat, live notifications, collaborative editing, multiplayer games, and real-time dashboards. For Next.js/serverless apps, use a managed service like Pusher or Supabase Realtime instead of raw WebSockets.

HTTP vs. WebSockets

Normal HTTP is like mailing letters: you send a request, wait for a response, and the connection closes. Every piece of data requires a new round-trip. This works fine for loading pages and fetching data, but is inefficient for real-time use cases.

WebSockets are like a phone call: you establish a connection once, and then both sides can talk freely until someone hangs up. The server can push data to the client without the client asking first.

HTTP polling

Client asks "anything new?" every 2 seconds. Works but wasteful — 95% of requests return nothing. Adds latency equal to the polling interval.

WebSocket

Server pushes data the moment it is available. Zero polling overhead. Sub-100ms latency. More complex to set up and scale.

How a WebSocket Connection Works

  1. Browser makes a special HTTP request with Upgrade: websocket header
  2. Server responds with 101 Switching Protocols — the connection upgrades from HTTP to WebSocket
  3. The TCP connection stays open — both sides can now send messages freely
  4. Either side can close the connection with a close frame

The WebSocket URL uses ws:// (unencrypted) or wss:// (encrypted, like HTTPS). Always use wss:// in production.

Native Browser WebSocket API

// Browser-side (built into all modern browsers)
const socket = new WebSocket('wss://myapp.com/ws')

socket.onopen = () => {
  console.log('Connected')
  socket.send(JSON.stringify({ type: 'join', room: 'general' }))
}

socket.onmessage = (event) => {
  const data = JSON.parse(event.data)
  console.log('Received:', data)
}

socket.onclose = () => {
  console.log('Disconnected')
}

socket.onerror = (error) => {
  console.error('WebSocket error:', error)
}
// Server-side with Node.js 'ws' library
import { WebSocketServer } from 'ws'
const wss = new WebSocketServer({ port: 8080 })

wss.on('connection', (ws) => {
  console.log('Client connected')

  ws.on('message', (data) => {
    const message = JSON.parse(data.toString())
    // Broadcast to all connected clients
    wss.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(message))
      }
    })
  })

  ws.on('close', () => console.log('Client disconnected'))
})

Socket.io: The Practical Choice

Most AI-generated real-time apps use Socket.io rather than raw WebSockets. Socket.io adds:

  • Automatic reconnection — if the connection drops, it reconnects without you writing reconnect logic
  • Rooms — broadcast to subsets of connected clients (io.to('room-name').emit(...))
  • Namespaces — multiple independent communication channels on one connection
  • Fallback — falls back to HTTP long-polling when WebSockets are blocked by firewalls
  • Event-based API — named events instead of raw messages
// Socket.io server
import { Server } from 'socket.io'
const io = new Server(httpServer)

io.on('connection', (socket) => {
  socket.on('chat:message', (msg) => {
    io.emit('chat:message', msg) // Broadcast to all
  })
  socket.on('join:room', (room) => {
    socket.join(room)
    io.to(room).emit('user:joined', socket.id)
  })
})

// Socket.io client
import { io } from 'socket.io-client'
const socket = io('wss://myapp.com')
socket.emit('chat:message', { text: 'Hello!' })
socket.on('chat:message', (msg) => renderMessage(msg))

When Not to Use WebSockets

  • Serverless/Next.js API routes: Serverless functions close after returning a response — they cannot hold a persistent connection. Use Pusher, Ably, Supabase Realtime, or PartyKit instead.
  • Infrequent updates: If data changes once per minute, HTTP polling with a 60-second interval is simpler and adequate.
  • One-way server streaming: Server-Sent Events (SSE) are simpler than WebSockets for one-direction streaming (e.g., AI response streaming, live log output).

What to Learn Next

Next Step

If you need real-time in a Next.js or serverless project, start with Supabase Realtime or Pusher. They manage the WebSocket infrastructure and give you a clean API. Save raw WebSocket/Socket.io for dedicated Node.js server projects where you control the runtime.

FAQ

WebSockets are a protocol that enables a persistent, two-way connection between a browser and a server. Unlike HTTP (request-response), a WebSocket connection stays open and both sides can send messages at any time — enabling real-time features like chat, live notifications, and multiplayer games.

Use WebSockets when you need low-latency real-time communication or when the server needs to push data without the client asking. Use polling for data that updates infrequently or when WebSocket infrastructure is not available. Server-Sent Events (SSE) is a middle ground for one-way server-to-client streaming.

Socket.io is a JavaScript library that wraps WebSockets with fallbacks and extra features — automatic reconnection, rooms (broadcast groups), namespaces, and fallback to HTTP long-polling when WebSockets are blocked. It is the most popular way to add real-time functionality to Node.js apps.

WebSockets do not work with Next.js serverless API routes because serverless functions close after returning a response. For WebSockets with Next.js, use a service like Pusher, Ably, or Supabase Realtime, or run a separate Node.js server alongside Next.js.

WebSocket connections cannot send HTTP headers after the initial handshake. Common patterns: pass a JWT as a query parameter during the initial connection URL (over HTTPS only), or send authentication as the first message after connecting.