GSoft Consulting
Web Development

Real-Time Dashboards with Server-Sent Events and Next.js 15

AA

Ahmed Anis

CTO

14 November 2025

7 min read
Real-Time Dashboards with Server-Sent Events and Next.js 15
Web Development

Every time a product team asks us to add real-time updates to their dashboard, the first suggestion is WebSockets. It's the intuitive choice — bidirectional, low-latency, widely supported. But for dashboards that stream server-to-client data (metrics, logs, live activity feeds), Server-Sent Events are almost always the better choice. They're simpler, cheaper to operate, and work seamlessly with Next.js's streaming model.

When SSE Beats WebSockets

  • One-directional streams: If the client only reads and never sends, SSE is purpose-built for this. No bidirectional handshake overhead, no upgrade negotiation.
  • HTTP/2 multiplexing: SSE runs over standard HTTP/2, which means multiple event streams share a single connection. WebSockets require their own persistent TCP connection per stream.
  • Automatic reconnect: The browser EventSource API handles reconnection automatically with the Last-Event-ID header. WebSocket reconnection logic is your responsibility.
  • CDN and proxy compatibility: SSE works transparently through Vercel, Cloudflare, and any standard proxy. WebSockets need explicit pass-through configuration.

The Next.js Route Handler Pattern

TYPESCRIPTcode
// app/api/metrics/stream/route.ts
import { metricsEmitter } from '@/lib/metrics'

export async function GET() {
  const stream = new ReadableStream({
    start(controller) {
      const encoder = new TextEncoder()

      const send = (data: object) => {
        controller.enqueue(
          encoder.encode(`data: ${JSON.stringify(data)}\n\n`)
        )
      }

      metricsEmitter.on('update', send)

      // Heartbeat every 30s to keep connection alive
      const heartbeat = setInterval(() => {
        controller.enqueue(encoder.encode(': keep-alive\n\n'))
      }, 30_000)

      return () => {
        metricsEmitter.off('update', send)
        clearInterval(heartbeat)
      }
    },
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  })
}

💡 Buffer events during reconnection

Store the last N events server-side keyed by event ID. When a client reconnects with Last-Event-ID, replay missed events before resuming the live stream. This prevents data gaps during transient network issues.

React Hook for SSE Consumption

TYPESCRIPTcode
// hooks/useEventStream.ts
import { useEffect, useState } from 'react'

export function useEventStream<T>(url: string) {
  const [data, setData] = useState<T | null>(null)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    const source = new EventSource(url)

    source.onmessage = (e) => {
      try {
        setData(JSON.parse(e.data) as T)
      } catch {
        setError(new Error('Failed to parse event data'))
      }
    }

    source.onerror = () => {
      setError(new Error('SSE connection failed'))
    }

    return () => source.close()
  }, [url])

  return { data, error }
}

20+

Real-time dashboards built

Lower server overhead vs WebSockets

99.8%

Avg SSE connection reliability

<50ms

Event delivery latency (p50)

Tags

Next.jsReal-timeSSEWebSocketsReactStreaming

Work with us

Ready to build your product?

We help product teams across the UK, Netherlands, Australia, and North America ship faster without compromising quality. Let's talk about your project.

Talk to our team →