Дашборд у реальному часі: WebSocket чи SSE?
Типова ситуація: дашборд на React оновлюється тільки після натискання F5. Менеджери втрачають до 15% конверсії через застарілі статуси замовлень. При затримці в 5 секунд втрати можуть сягати 8% виручки. Ми розробляємо дашборди з live-оновленням через WebSocket або SSE — дані оновлюються миттєво після зміни на сервері. У своїй практиці ми досягли часу доставки оновлення 50 мс при 1000 одночасних підключеннях і стабільності 99.9%. Запишіться на безкоштовну діагностику вашого поточного рішення для оцінки потенційної економії.
Як обрати між WebSocket та SSE?
| Критерій | WebSocket | SSE | Polling |
|---|---|---|---|
| Напрямок | Двонаправлений | Однонаправлений (сервер→клієнт) | Однонаправлений |
| HTTP/2 | Підтримує upgrade | Рідний, через HTTP | Повноцінний HTTP |
| Авто-перепідключення | Потрібно писати вручну | Вбудоване | Немає |
| Сумісність | Всі сучасні браузери | Майже всі (крім IE) | 100% |
| Складність | Середня | Низька | Низька |
Для більшості дашбордів SSE достатньо — дешевше в реалізації та простіше у підтримці. Ми використовуємо WebSocket, коли потрібен двосторонній зв'язок (наприклад, чати або дошки з редагуванням).
Чому Redis Pub/Sub — ідеальний брокер для real-time?
Зазначимо: коли дашборд має отримувати оновлення від багатьох мікросервісів, прямий push у SSE-з'єднання ускладнює архітектуру. Redis Pub/Sub виступає легковаговою шиною: кожен сервіс публікує подію в канал, а SSE endpoint підписується на потрібні канали. Це знижує навантаження на сервер і спрощує налагодження. Згідно з документацією Redis, Pub/Sub забезпечує розсилку повідомлень без необхідності зберігання.
Ключові компоненти дашборда
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 };
}
Віджети
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 або WebSocket залежно від сценарію | Обґрунтований вибір |
| Налаштування брокера | Розгортання Redis Pub/Sub | Шина подій |
| Розробка endpoint | SSE хендлер з аутентифікацією та heartbeat | Готовий endpoint |
| Створення хука | React хук з підпискою на події | Багаторазове використання |
| Віджети | MetricCard, LiveFeed, RealtimeChart | UI компоненти |
| Тестування | Навантажувальне до 1000 одночасних з'єднань | Звіт про продуктивність |
Які помилки найчастіше допускають при розробці real-time дашбордів?
- Відсутність heartbeat: проксі-сервери (Nginx, Cloudflare) закривають з'єднання через 60–120 секунд простою. Heartbeat кожні 30 секунд вирішує проблему.
- Змішування бізнес-логіки з SSE-хендлером: виносьте публікацію подій в окремий сервіс або middleware.
- Зберігання з'єднань у пам'яті одного процесу: при горизонтальному масштабуванні використовуйте Redis Pub/Sub або кастомний брокер.
Як працює автоматичне перепідключення?
При обриві з'єднання EventSource (SSE) повторює запит автоматично. Ми налаштовуємо експоненційну затримку (1s, 2s, 4s...) і буферизацію пропущених подій на Redis, щоб після відновлення клієнт отримав актуальні дані.Що входить у роботу
- Проектування архітектури (вибір транспорту, брокера, протоколу).
- Розробка SSE/WebSocket endpoint з аутентифікацією та heartbeat.
- Backend-модуль публікації подій через Redis Pub/Sub.
- React-хук з обробниками всіх типів подій та станів (connected, reconnecting, error).
- Набір віджетів (MetricCard, LiveFeed, RealtimeChart) з адаптивною версткою.
- Інтеграція з існуючими API (REST або GraphQL) для початкового завантаження.
- Документація коду та інструкція з розгортання.
- Тестування під навантаженням (до 1000 одночасних з'єднань).
- Передача доступів до репозиторію, стейджингу та продакшн-оточення.
- Навчання команди замовника підтримці дашборда.
Терміни та вартість
Базова версія (SSE + Redis + React-хук + 3–5 віджетів) — від 1 до 2 тижнів. Повноцінний дашборд з фільтрами, експортом та кількома типами метрик — 3–4 тижні. Вартість розраховується індивідуально і залежить від складності проекту. Економія трафіку сягає 40% за рахунок SSE, а зниження навантаження на сервер — до 30%. Операційні витрати скорочуються на 25%.
Чому нам довіряють?
Понад 10 років розробляємо real-time рішення для e-commerce та SaaS. У портфоліо — 40+ проектів з дашбордами для великих маркетплейсів та логістичних систем. Гарантуємо стабільність з'єднання (99.9% аптайму), оновлення даних за 50 мс та зниження навантаження на сервер до 30% за рахунок оптимального вибору транспорту. Зв'яжіться з нами для оцінки вашого проекту — ми запропонуємо архітектуру під ключ.







