Sincronización en Tiempo Real con WebSockets y Server-Sent Events (SSE) en Next.js 15

La interactividad en tiempo real se ha convertido en un requisito indispensable para aplicaciones web modernas: tableros de control financiero, monitoreo IoT, chats en vivo y notificaciones de pedidos. En Next.js 15, elegir la arquitectura correcta entre Server-Sent Events (SSE) y WebSockets es determinante para el rendimiento y la escalabilidad de la infraestructura. En esta guía técnica analizamos cuándo utilizar cada protocolo y cómo implementarlos paso a paso.

Comparativa: WebSockets vs Server-Sent Events (SSE)#
Antes de escribir código, es crucial comprender las diferencias fundamentales entre ambas tecnologías:
| Característica | Server-Sent Events (SSE) | WebSockets (WS) |
|---|---|---|
| Direccionalidad | Unidireccional (Servidor → Cliente) | Bidireccional full-duplex (Servidor ↔ Cliente) |
| Protocolo | HTTP estándar (HTTP/1.1 chunked, HTTP/2 multiplexado) | Protocolo TCP independiente (ws:// / wss://) |
| Reconexión Automática | Nativa en el navegador vía EventSource |
Manual o dependiente de librerías (Socket.io) |
| Soporte Proxy / Firewalls | Excelente (funciona sobre puertos 80/443 sin cambios) | Requiere soporte de Upgrade header en proxies |
| Caso de Uso Ideal | Notificaciones, feeds de noticias, tickers bursátiles, IA streaming | Chats colaborativos, juegos multijugador, pizarras compartidas |
Arquitectura de Tiempo Real con Next.js y Redis Pub/Sub#
Cuando escalamos a múltiples instancias de servidor (servidores stateless o clusters en Docker/Kubernetes), necesitamos un bus de mensajes centralizado para que un evento originado en el Servidor A llegue a los clientes conectados al Servidor B.
+-------------------------------------------------------------------------------+
| CLIENTES (Navegadores Web) |
| +-----------------------+ +--------------------------------+ |
| | Dashboard SSE Client | | Chat WebSockets Client | |
| +-----------------------+ +--------------------------------+ |
+-------------------------------------------------------------------------------+
| (EventSource GET /api/sse) | (WSS Connection)
v v
+-----------------------------------+ +------------------------------------+
| NEXT.js 15 APP ROUTER INSTANCE 1 | | NODE.JS WS GATEWAY INSTANCE 2 |
| (Route Handlers Streaming API) | | (ws / Socket.io Server) |
+-----------------------------------+ +------------------------------------+
\ /
\ /
v v
+-------------------------------------------------------------------------------+
| BUS DE MENSAJES DISTRIBUIDO (REDIS) |
| Redis Pub/Sub (Channels: "alerts", "live-orders") |
+-------------------------------------------------------------------------------+
Implementación 1: Server-Sent Events (SSE) con Next.js 15 App Router#
Next.js 15 permite crear endpoints SSE ultra-eficientes utilizando la API estándar de ReadableStream en Route Handlers (app/api/events/route.ts).
Endpoint SSE en Next.js (src/app/api/events/route.ts)#
import { NextRequest } from 'next/server';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export async function GET(req: NextRequest) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
// Enviar evento de bienvenida
controller.enqueue(
encoder.encode(`event: connected
data: ${JSON.stringify({ message: "Conectado al stream SSE" })}
`)
);
// Intervalo de latido / actualización (Heartbeat cada 5s)
const intervalId = setInterval(() => {
const payload = {
timestamp: new Date().toISOString(),
activeUsers: Math.floor(Math.random() * 200) + 50,
cpuUsage: (Math.random() * 30 + 10).toFixed(1) + "%",
};
controller.enqueue(
encoder.encode(`event: metrics
data: ${JSON.stringify(payload)}
`)
);
}, 5000);
// Limpieza al cerrar la conexión
req.signal.addEventListener('abort', () => {
clearInterval(intervalId);
controller.close();
});
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // Evita buffering en NGINX
},
});
}
Consumo en Cliente React con EventSource (DashboardMetrics.tsx)#
'use client';
import { useEffect, useState } from 'react';
interface Metrics {
timestamp: string;
activeUsers: number;
cpuUsage: string;
}
export function DashboardMetrics() {
const [metrics, setMetrics] = useState<Metrics | null>(null);
const [status, setStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting');
useEffect(() => {
const eventSource = new EventSource('/api/events');
eventSource.addEventListener('connected', () => {
setStatus('connected');
});
eventSource.addEventListener('metrics', (event) => {
const data: Metrics = JSON.parse(event.data);
setMetrics(data);
});
eventSource.onerror = () => {
setStatus('disconnected');
// El navegador reintentará automáticamente
};
return () => {
eventSource.close();
};
}, []);
return (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-xl text-white">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-bold">Telemetría en Vivo</h3>
<span className={`inline-block w-3 h-3 rounded-full ${status === 'connected' ? 'bg-emerald-500 animate-pulse' : 'bg-rose-500'}`} />
</div>
{metrics ? (
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-800 rounded-lg">
<p className="text-sm text-slate-400">Usuarios Activos</p>
<p className="text-2xl font-semibold text-emerald-400">{metrics.activeUsers}</p>
</div>
<div className="p-4 bg-slate-800 rounded-lg">
<p className="text-sm text-slate-400">Uso de CPU</p>
<p className="text-2xl font-semibold text-cyan-400">{metrics.cpuUsage}</p>
</div>
</div>
) : (
<p className="text-slate-400">Esperando primer paquete de métricas...</p>
)}
</div>
);
}
Implementación 2: Servidor WebSockets Dedicado con Redis Adapter#
Para interacciones bidireccionales de baja latencia (como chats colaborativos o edición simultánea), desacoplar el servidor de WebSockets en una instancia Node.js independiente con Socket.io y @socket.io/redis-adapter asegura escalabilidad horizontal ilimitada.
// server/websocket-gateway.ts
import http from 'http';
import { Server } from 'socket.io';
import { createClient } from 'redis';
import { createAdapter } from '@socket.io/redis-adapter';
const server = http.createServer();
const io = new Server(server, {
cors: {
origin: ["https://brayan.es", "http://localhost:3000"],
methods: ["GET", "POST"]
}
});
async function bootstrap() {
const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
io.on('connection', (socket) => {
console.log(`[WS] Cliente conectado: ${socket.id}`);
socket.on('join_room', (roomId: string) => {
socket.join(roomId);
});
socket.on('send_message', (data: { roomId: string; message: string; author: string }) => {
// Re-emitir a todos los usuarios en la sala
io.to(data.roomId).emit('new_message', {
...data,
createdAt: new Date().toISOString()
});
});
socket.on('disconnect', () => {
console.log(`[WS] Cliente desconectado: ${socket.id}`);
});
});
const PORT = process.env.PORT || 4000;
server.listen(PORT, () => {
console.log(`🚀 Servidor WebSockets escuchando en el puerto ${PORT}`);
});
}
bootstrap();
Buenas Prácticas de Rendimiento y Seguridad#
- Evitar sobrecargar el hilo principal de Node.js: Enviar únicamente deltas de datos (cambios) en lugar del estado completo en cada evento.
- Heartbeat y Detección de Caídas: Enviar pings regulares para detectar conexiones fantasma (ghost sockets) y liberar memoria en el servidor.
- Autenticación con JWT en el Handshake: Validar el token de sesión durante la conexión inicial y asociar el socket al
userIdverificado. - Protección DDoS y Rate Limiting: Limitar el número máximo de mensajes por segundo por IP utilizando Redis Token Bucket.
Conclusión#
La combinación de Server-Sent Events para flujos unidireccionales (notificaciones y métricas) y WebSockets con Redis para aplicaciones colaborativas bidireccionales ofrece el equilibrio perfecto entre simplicidad, rendimiento y costo de infraestructura.
¿Necesitas diseñar una arquitectura en tiempo real escalable para tu plataforma web o SaaS? Revisa nuestros Servicios de Desarrollo de Software o agenda una llamada técnica con Brayan Developer.
¿Te gustaría profundizar en estos temas?
Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.


