Реализация Real-Time Dashboard (обновление данных без перезагрузки)
Real-Time Dashboard отображает актуальные данные без ручного обновления страницы — метрики, статусы заказов, аналитику, KPI. Обновление происходит через WebSocket или SSE в момент изменения данных.
Выбор технологии
WebSocket — двунаправленный, подходит для интерактивных дашбордов с фильтрами. Server-Sent Events (SSE) — однонаправленный сервер → клиент, проще, работает через HTTP/2. Polling — каждые N секунд делать запрос. Просто, но менее эффективно.
Для дашборда без пользовательских действий — SSE достаточно и дешевле в реализации.
SSE Endpoint
// GET /api/dashboard/stream
app.get('/api/dashboard/stream', authenticate, async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no'); // для nginx
const sendEvent = (event: string, data: unknown) => {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// Отправить начальные данные
const initial = await dashboardService.getMetrics(req.user.id);
sendEvent('init', initial);
// Подписаться на Redis pub/sub для обновлений
const subscriber = redis.duplicate();
await subscriber.subscribe(`dashboard:${req.user.id}`);
subscriber.on('message', (channel, message) => {
const update = JSON.parse(message);
sendEvent(update.type, update.data);
});
// Heartbeat каждые 30 сек чтобы не закрылось соединение
const heartbeat = setInterval(() => {
res.write(':heartbeat\n\n');
}, 30000);
req.on('close', () => {
clearInterval(heartbeat);
subscriber.unsubscribe();
subscriber.quit();
});
});
React Hook для SSE
function useDashboardStream(userId: string) {
const [metrics, setMetrics] = useState<DashboardMetrics | null>(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const eventSource = new EventSource('/api/dashboard/stream', {
withCredentials: true
});
eventSource.addEventListener('init', (e) => {
setMetrics(JSON.parse(e.data));
setIsConnected(true);
});
eventSource.addEventListener('metrics:updated', (e) => {
const update = JSON.parse(e.data);
setMetrics(prev => prev ? { ...prev, ...update } : update);
});
eventSource.addEventListener('order:new', (e) => {
const order = JSON.parse(e.data);
setMetrics(prev => prev ? {
...prev,
todayOrders: prev.todayOrders + 1,
todayRevenue: prev.todayRevenue + order.total
} : null);
});
eventSource.onerror = () => {
setIsConnected(false);
// EventSource автоматически переподключается
};
return () => eventSource.close();
}, [userId]);
return { metrics, isConnected };
}
Публикация обновлений из бэкенда
// При создании заказа — уведомить все активные дашборды
async function onOrderCreated(order: Order) {
// Опубликовать для менеджера
await redis.publish(`dashboard:${order.managerId}`, JSON.stringify({
type: 'order:new',
data: { id: order.id, total: order.total, status: order.status }
}));
// Опубликовать в общий канал для admin-дашборда
await redis.publish('dashboard:admin', JSON.stringify({
type: 'metrics:updated',
data: { todayOrders: await countTodayOrders(), todayRevenue: await sumTodayRevenue() }
}));
}
Дашборд с Socket.IO и виджетами
function DashboardPage() {
const { metrics, isConnected } = useDashboardStream(user.id);
return (
<div className="dashboard-grid">
<ConnectionIndicator isConnected={isConnected} />
<MetricCard
title="Заказы сегодня"
value={metrics?.todayOrders ?? 0}
delta={metrics?.ordersVsYesterday}
/>
<MetricCard
title="Выручка"
value={formatCurrency(metrics?.todayRevenue ?? 0)}
delta={metrics?.revenueVsYesterday}
/>
<LiveOrderFeed orders={metrics?.recentOrders ?? []} />
<RealtimeChart
data={metrics?.hourlyRevenue ?? []}
title="Выручка по часам"
/>
</div>
);
}
Сроки
SSE endpoint + Redis pub/sub + React-хук + базовые виджеты — 1–2 недели. Полный дашборд с фильтрами, экспортом и несколькими типами метрик — 3–4 недели.







