Building Real-Time UIs with React Server-Sent Events (SSE)

Real-time data synchronization is an essential requirement for modern web applications. Whether you are building live stock tickers, chat applications, collaborative workspaces, or live notification feeds, delivering instant updates to users without requiring manual page refreshes elevates user experience and system responsiveness.

While WebSockets have traditionally been the go-to protocol for real-time bidirectional communication, they can introduce unnecessary complexity—such as managing custom connection handshakes, handling manual reconnection loops, proxy configurations, and state synchronization across reconnections.

For unidirectional data streaming—where the server pushes updates down to the client—**Server-Sent Events (SSE)** provide a lightweight, robust, and standards-compliant alternative built directly on standard HTTP.

This comprehensive guide explores how to implement Server-Sent Events in React and Next.js applications, manage persistent event streams, handle automatic reconnections, and build responsive real-time user interfaces.

SSE vs. WebSockets: When to Use Server-Sent Events

Before implementing SSE in React, it is helpful to understand how it compares to alternative real-time transport mechanisms:

• HTTP Short Polling: The client repeatedly requests data at fixed intervals. This creates massive network overhead, database load, and unnecessary latency.

• WebSockets: Provides full-duplex, bidirectional communication over a single TCP socket. Ideal for chat apps, multiplayer games, and real-time collaborative editing where both client and server send frequent messages.

• Server-Sent Events (SSE): Enables a server to stream data to the client over a single long-lived HTTP connection using the standard `text/event-stream` MIME type. SSE supports automatic reconnection out of the box, standard HTTP authentication, and firewall compatibility because it relies on standard HTTP GET requests.

Creating an Event Stream API Route in Next.js

To stream data from a Next.js backend to your React frontend, you can create an API route that sets the appropriate headers and keeps the response stream open using Node.js stream primitives or Web Response streams.

TypeScript
Next.js API route establishing a Server-Sent Events stream.
export const dynamic = 'force-dynamic';

export async function GET(request: Request) {
  const encoder = new TextEncoder();

  const customStream = new ReadableStream({
    start(controller) {
      const interval = setInterval(() => {
        const data = JSON.stringify({
          timestamp: new Date().toISOString(),
          metric: Math.floor(Math.random() * 100),
        });

        controller.enqueue(encoder.encode(`data: ${data}\n\n`));
      }, 3000);

      request.signal.addEventListener('abort', () => {
        clearInterval(interval);
        controller.close();
      });
    },
  });

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

Building a Custom React Hook for EventSource

The browser provides a built-in `EventSource` API for connecting to SSE endpoints. To consume this event stream cleanly inside React components, you can encapsulate the connection lifecycle within a custom React hook.

TSX
Custom React hook for handling Server-Sent Events and state updates.
'use client';

import { useState, useEffect } from 'react';

interface StreamData {
  timestamp: string;
  metric: number;
}

export function useServerSentEvents(url: string) {
  const [data, setData] = useState<StreamData | null>(null);
  const [isConnected, setIsConnected] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const eventSource = new EventSource(url);

    eventSource.onopen = () => {
      setIsConnected(true);
      setError(null);
    };

    eventSource.onmessage = (event) => {
      try {
        const parsedData = JSON.parse(event.data);
        setData(parsedData);
      } catch (err) {
        console.error('Failed to parse SSE message:', err);
      }
    };

    eventSource.onerror = (err) => {
      setIsConnected(false);
      setError('Connection lost. Reconnecting...');
    };

    return () => {
      eventSource.close();
    };
  }, [url]);

  return { data, isConnected, error };
}

Displaying Live Metrics in React Components

With your custom hook established, you can render live data streams directly inside your React components, showing connection indicators and updating UI metrics in real-time.

TSX
Real-time dashboard component consuming the SSE hook.
'use client';

import { useServerSentEvents } from '@/hooks/useServerSentEvents';

export default function LiveDashboard() {
  const { data, isConnected, error } = useServerSentEvents('/api/events');

  return (
    <div className="p-8 max-w-xl mx-auto bg-white rounded-2xl shadow">
      <div className="flex justify-between items-center mb-6">
        <h1 className="text-2xl font-bold text-gray-900">Live System Monitor</h1>
        <span
          className={`px-3 py-1 text-xs font-semibold rounded-full ${
            isConnected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
          }`}
        >
          {isConnected ? 'Connected' : 'Disconnected'}
        </span>
      </div>

      {error && <p className="text-amber-600 text-sm mb-4">{error}</p>}

      <div className="grid grid-cols-2 gap-4">
        <div className="p-4 bg-gray-50 rounded-xl">
          <p className="text-sm text-gray-500">Live Metric Value</p>
          <p className="text-3xl font-extrabold text-indigo-600 mt-1">
            {data ? data.metric : '--'}
          </p>
        </div>
        <div className="p-4 bg-gray-50 rounded-xl">
          <p className="text-sm text-gray-500">Last Updated</p>
          <p className="text-sm font-medium text-gray-800 mt-2">
            {data ? new Date(data.timestamp).toLocaleTimeString() : 'Waiting for stream...'}
          </p>
        </div>
      </div>
    </div>
  );
}

Summary

Server-Sent Events provide an elegant, lightweight solution for streaming real-time updates from server to client over standard HTTP connections without the overhead of WebSockets.

By wrapping browser `EventSource` connections in custom React hooks and pairing them with Next.js streaming API routes, developers can build robust, reactive user interfaces with automatic reconnection handling and minimal latency.