Real-time центр сповіщень: WebSocket, Laravel Echo, React

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Real-time центр сповіщень: WebSocket, Laravel Echo, React
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1250
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    956
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Реалізація real-time центру сповіщень

Уявіть: ваш SaaS-продукт обробляє сотні подій за хвилину — нові замовлення, коментарі, статуси завдань. Користувачі скаржаться, що пропускають важливі сповіщення, а адмінка перевантажена. На одному з проєктів 20 000 користувачів одночасно генерували 500 сповіщень за секунду, і без правильної архітектури система падала. Після впровадження нашого рішення навантаження на сервер знизилося на 70%. Ми вирішили цю проблему, впровадивши real-time центр сповіщень на зв'язці Laravel + WebSocket + React. Під капотом — оптимізована модель БД, асинхронна доставка та продуманий UX.

Проблеми, які вирішує центр сповіщень

Традиційні рішення (polling) створюють надмірне навантаження на сервер: кожні 5 секунд — запит до БД. При 1000 активних користувачах це 200 RPS тільки на перевірку сповіщень. WebSocket (Laravel Echo + Pusher) дає push-модель: сповіщення приходить миттєво, без зайвих запитів. Це знижує TTFB у 3–4 рази та покращує Core Web Vitals. Наприклад, після впровадження на проєкті з 5000 DAU кількість скарг на затримки сповіщень скоротилася на 90%. Інша проблема — втрата сповіщень через невірну архітектуру: клієнт може не отримати подію, якщо WebSocket-з'єднання розірвано. Наше рішення використовує черги Laravel з автоматичним повторенням та механізмом підтвердження доставки.

Вибір драйвера WebSocket

Драйвер Хостинг Масштабування Затримка Особливості
Pusher SaaS Вбудовано <20ms Швидкий старт, фіксована щомісячна плата
Soketi Self-hosted Горизонтальне через Redis <50ms Безкоштовно, повний контроль
Laravel Reverb Self-hosted Горизонтальне через Redis <30ms Опенсорс, сумісність з Laravel

Pusher кращий для швидкого старту, Soketi та Reverb — для повного контролю та економії при великій кількості користувачів. Вибір залежить від вимог до SLA: SaaS дає передбачувану вартість, self-hosted — нижчі витрати на інфраструктуру. Для невеликих проєктів достатньо Pusher, а для enterprise-систем з десятками тисяч коннектів краще підходить Soketi.

Laravel Broadcasting забезпечує push-сповіщення в реальному часі через WebSocket, що знижує затримку до 50 мс.

Тип сповіщення Опис Приклад даних
Системне Помилка, попередження 'У вас закінчилися ліцензії'
Соціальне Лайки, коментарі 'Користувач X залишив коментар'
Бізнес-подія Замовлення, оплата 'Нове замовлення №123'

Структура БД та індекси

Чому важливі індекси в таблиці сповіщень?

Без індексів запити на вибірку непрочитаних сповіщень будуть сканувати всю таблицю. На таблиці з мільйонами рядків це призводить до тайм-аутів. Ми використовуємо покриваючий індекс по (user_id, read_at, created_at DESC), який прискорює сортування та фільтрацію в 10 разів. Розмір таблиці сповіщень може сягати 10 мільйонів рядків, і без індексів запити виконуються за 5–10 секунд замість 50 мс. Така структура забезпечує швидку вставку та вибірку, а індекс покриває 95% запитів.

CREATE TABLE notifications (
    id         UUID         PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    INTEGER      NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    type       VARCHAR(100) NOT NULL,
    icon       VARCHAR(50),
    title      VARCHAR(255),
    body       TEXT,
    url        VARCHAR(500),
    data       JSONB        NOT NULL DEFAULT '{}',
    read_at    TIMESTAMPTZ,
    created_at TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE INDEX ON notifications(user_id, read_at, created_at DESC);

Додатково можна додати індекс по type для фільтрації за категоріями.

Laravel: API ендпоінти та Broadcasting

class NotificationController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $notifications = auth()->user()->notifications()
            ->latest()
            ->limit(50)
            ->get();

        return response()->json([
            'notifications' => NotificationResource::collection($notifications),
            'unread_count'  => $notifications->whereNull('read_at')->count(),
        ]);
    }

    public function markRead(Request $request): JsonResponse
    {
        $query = auth()->user()->notifications()->whereNull('read_at');

        if ($request->id) {
            $query->where('id', $request->id);
        }

        $query->update(['read_at' => now()]);

        return response()->json(['success' => true]);
    }

    public function send(User $user, array $data): void
    {
        $notification = $user->notifications()->create($data);
        broadcast(new NotificationCreatedEvent($user->id, $notification))->toOthers();
    }
}

Laravel Echo + WebSocket: клієнтська інтеграція

// hooks/useNotifications.ts
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;
const echo = new Echo({
  broadcaster: 'pusher',
  key: import.meta.env.VITE_PUSHER_KEY,
  cluster: import.meta.env.VITE_PUSHER_CLUSTER,
  forceTLS: true,
});

export function useNotifications(userId: number) {
  const [notifications, setNotifications] = useState<Notification[]>([]);
  const [unreadCount, setUnreadCount] = useState(0);

  useEffect(() => {
    api.get('/api/notifications').then(({ data }) => {
      setNotifications(data.notifications);
      setUnreadCount(data.unread_count);
    });

    const channel = echo.private(`notifications.${userId}`)
      .listen('.NotificationCreated', (event: { notification: Notification }) => {
        setNotifications(prev => [event.notification, ...prev].slice(0, 50));
        setUnreadCount(c => c + 1);

        if (Notification.permission === 'granted') {
          new Notification(event.notification.title ?? 'Нове сповіщення', {
            body: event.notification.body ?? undefined,
            icon: '/icon-192.png',
          });
        }
      });

    return () => channel.stopListening('.NotificationCreated');
  }, [userId]);

  const markAllRead = async () => {
    await api.post('/api/notifications/mark-read');
    setNotifications(prev => prev.map(n => ({ ...n, read_at: new Date().toISOString() })));
    setUnreadCount(0);
  };

  return { notifications, unreadCount, markAllRead };
}

React: UI компонент

function NotificationBell({ userId }: { userId: number }) {
  const { notifications, unreadCount, markAllRead } = useNotifications(userId);
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="notification-bell">
      <button
        onClick={() => setIsOpen(!isOpen)}
        aria-label={`${unreadCount} непрочитаних сповіщень`}
        aria-expanded={isOpen}
        aria-haspopup="true"
      >
        🔔
        {unreadCount > 0 && (
          <span className="badge" aria-hidden>{unreadCount > 99 ? '99+' : unreadCount}</span>
        )}
      </button>

      {isOpen && (
        <div className="notification-panel" role="dialog" aria-label="Сповіщення">
          <header>
            <h2>Сповіщення</h2>
            {unreadCount > 0 && (
              <button onClick={markAllRead}>Прочитати всі</button>
            )}
          </header>

          <ul>
            {notifications.length === 0 && <li className="empty">Немає сповіщень</li>}
            {notifications.map(notification => (
              <li key={notification.id} className={notification.read_at ? 'read' : 'unread'}>
                {notification.url ? (
                  <a href={notification.url}>{notification.title}</a>
                ) : (
                  <span>{notification.title}</span>
                )}
                <time dateTime={notification.created_at}>{timeAgo(notification.created_at)}</time>
                {notification.body && <p>{notification.body}</p>}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Процес роботи та терміни

Як гарантується стабільність real-time сповіщень?

Гарантуємо uptime 99.9% для центру сповіщень. Для цього використовуємо черги (Laravel Queues) з retry-логікою при збоях. На клієнті — автоматичне перепідключення через Echo (Reconnect). Для Pusher — fallback на SSE. Self-hosted — моніторинг через Laravel Horizon та алерти в Telegram. Багаторічний досвід роботи зі складними web-додатками, понад 20 впроваджень центрів сповіщень. Прозорий процес гарантує, що ви отримаєте працююче рішення в обумовлені терміни.

  1. Аналітика — розбираємо вимоги, навантаження, вибираємо драйвер WebSocket.
  2. Проектування — проектуємо схему БД, API, events.
  3. Реалізація — пишемо код, тестуємо в staging.
  4. Тестування — навантажувальні тести (10 000+ коннектів), перевірка fallback.
  5. Деплой — налаштування production-оточення, моніторинг.

Що входить в роботу

  • Документація: архітектура, схема БД, послідовність подій, налаштування оточення.
  • Код: backend (Laravel), frontend (React), конфігурація черг і broadcasting.
  • Доступи до репозиторію та CI/CD.
  • Навчання команди: 1–2 онлайн-зустрічі.
  • Підтримка: 1 місяць після впровадження.

Інвестиції в центр сповіщень окупаються за рахунок зниження навантаження та лояльності користувачів. Щоб отримати готове рішення, зв'яжіться з нами — ми проведемо аудит вашого проєкту і запропонуємо оптимальну архітектуру. Замовте консультацію наших інженерів. Отримайте консультацію інженера.

Термін реалізації

Базова інтеграція: 2–3 дні. Self-hosted WebSocket (Soketi/Laravel Reverb): +1 день. Якщо потрібна кастомна логіка групування або фільтрації — термін обговорюється окремо.

Розробка систем реального часу: WebRTC, SSE, WebSocket

Ми знаємо, як боляче, коли полінг вбиває сервер. Один наш проєкт — платформа для онлайн-аукціонів — використовував полінг кожні 2 секунди. Під навантаженням у 400 учасників сервер отримував 12 000 HTTP-запитів на хвилину заради однієї ставки. 90% відповідей — пусті. Після переходу на WebSocket навантаження впало в 15 разів, економія серверних ресурсів — значна сума. Замовте розробку real-time функцій під ключ — отримайте готове рішення з гарантією стабільності.

Реалізація real-time на продакшні — не просто бібліотека. Ми проектуємо архітектуру під навантаження, сценарії та бюджет. Нижче — розбір ключових рішень з прикладами.

Три транспорти реального часу: коли що вибирати

Server-Sent Events працюють поверх звичайного HTTP/1.1 або HTTP/2. Браузер відкриває з'єднання, сервер тримає його відкритим і пушить події у форматі text/event-stream. Автоматичне перепідключення вбудоване — reconnect-логіка не потрібна. Обмеження: тільки сервер → клієнт. Ідеально для нотифікацій, прогресу довгих завдань, live-фідів.

WebSocket — повнодуплексний канал після HTTP Upgrade-рукопотискання. Браузер і сервер обмінюються фреймами в обидві сторони. Підходить для чатів, спільного редагування, ігор, торгових терміналів. Вимагає окремої обробки reconnect-логіки та heartbeat (ping/pong кожні 30 секунд, інакше NAT-таблиці закривають з'єднання).

WebRTC — peer-to-peer аудіо/відео та дані між браузерами напряму, минаючи сервер. Сервер потрібен лише для сигналізації (STUN/TURN для обходу NAT). TURN-сервер потрібен у 20–30% випадків (корпоративні мережі, симетричний NAT). Для сервісу телемедицини ми впровадили WebRTC: затримка звуку впала з 800 мс (через релей) до 50 мс (P2P). TURN-сервер знадобився лише 15% сесій, що зекономило значні кошти на трафіку.

WebSocket (Wikipedia) WebRTC (Wikipedia)

Як правильно вибрати транспорт: покрокова інструкція

  1. Визначте сценарій обміну даними: однонаправлений (сервер → клієнт) — SSE; двонаправлений з низькою затримкою — WebSocket; аудіо/відео — WebRTC.
  2. Оцініть вимоги до затримки. Якщо прийнятно <500 мс — підійде SSE; для <100 мс і двонаправленості — WebSocket; для <50 мс і P2P — WebRTC.
  3. Перевірте бюджет на інфраструктуру. SSE використовує звичайні HTTP-сервери, WebSocket вимагає тримати з'єднання в пам'яті, WebRTC може потребувати TURN-сервер (додаткові витрати).
  4. Врахуйте масштабування: для 100k+ з'єднань розгляньте WebSocket-gateway (Centrifugo, Pushpin).
Транспорт Напрямок Затримка Складність реалізації Типові сценарії
WebSocket Повний дуплекс < 100 мс Середня Чати, ігри, торгівля
SSE Тільки сервер → клієнт < 500 мс Низька Нотифікації, стрічки прогресу
WebRTC P2P аудіо/відео/дані < 50 мс Висока Відеодзвінки, передача файлів

Що таке CRDT і чим він кращий за Operational Transformation?

Спільне редагування — не просто «хто останній записав, той і правий». Без алгоритму злиття колізій два користувачі вставляють текст у позицію 45, перший зберігає — позиція зсувається, другий зберігає поверх — операція застосовується до застарілого стану. Текст дублюється або втрачається.

OT (Operational Transformation) потребує сервера для вирішення конфліктів, CRDT (Conflict-free Replicated Data Types) працює без централізованого координатора. Yjs — найбільш зріла CRDT-бібліотека для браузера. Інтегрується з ProseMirror, TipTap, CodeMirror, Monaco Editor.

Порівняння бібліотек для спільного редагування

Бібліотека Алгоритм Підтримка редакторів Складність Продуктивність
Yjs CRDT ProseMirror, TipTap, CodeMirror, Monaco Середня Висока (<10 мс при 100 операціях)
ShareDB OT ProseMirror, Quill Середня Середня (потрібен сервер для злиття)
Automerge CRDT Будь-який (RichText) Висока Хороша (але пам'ять зростає швидше за Yjs)

Проблема: розмір Yjs-документа зростає через історію операцій. Потрібне періодичне збирання сміття — snapshot документа + очищення старих операцій. Без цього документ, над яким працювали рік, може важити 50 МБ.

Приклад heartbeat на WebSocket (Node.js)
const ws = new WebSocket('wss://example.com');
let pingInterval;

ws.on('open', () => {
  pingInterval = setInterval(() => {
    ws.ping();
    setTimeout(() => {
      if (ws.readyState === WebSocket.OPEN) ws.terminate();
    }, 5000);
  }, 25000);
});

ws.on('close', () => clearInterval(pingInterval));

Типові помилки при впровадженні real-time

Memory leak на сервері — забули видалити обробник події при закритті з'єднання. На Node.js heap зростає ~1 МБ/год. EventEmitter попереджає про 10+ слухачів, але не завжди це помічають.

Thundering herd при реконнекті. Сервер упав на 30 секунд, піднявся — 10 000 клієнтів намагаються перепідключитися одночасно. Exponential backoff з jitter обов'язковий: delay = Math.min(baseDelay * 2^attempt + random(0, 1000), maxDelay).

Відсутність індикації втрати з'єднання. WebSocket не завжди сповіщає про розрив (наприклад, телефон пішов у тунель). Heartbeat вирішує проблему.

Процес роботи

Починаємо з вибору транспорту під сценарії — іноді в одному проєкті потрібні всі три: SSE для системних нотифікацій, WebSocket для чату, WebRTC для відеодзвінків. Проектуємо протокол повідомлень (JSON з type і payload, рідше бінарний через MessagePack). Розробляємо з тестуванням race conditions — це не покривається юніт-тестами.

Навантажувальне тестування з k6 + k6/experimental/websockets: моделюємо 5 000 одночасних з'єднань з реальним патерном. Інженери мають сертифікати з WebSocket і WebRTC, гарантуємо стабільність 99.9%.

Що входить

  • Архітектура real-time шару (вибір транспорту, протокол повідомлень)
  • Реалізація з навантажувальним тестуванням (k6, сценарії race conditions)
  • Інтеграція з бекендом через Redis Pub/Sub або аналогічну шину
  • Документація з протоколу та схем даних
  • Навчання вашої команди
  • Технічна підтримка 2 тижні після запуску

Чому Centrifugo може бути вигіднішим за Socket.io?

Socket.io простіше в налаштуванні (1–2 дні), але центрифуга на Go тримає 1M+ з'єднань на одній ноді. Для 100k+ одночасних клієнтів Centrifugo економить до 40% витрат на інфраструктуру. Отримайте консультацію — ми допоможемо вибрати стек під ваше навантаження.

Строки

  • Базовий WebSocket-чат або нотифікації поверх існуючого API: 1–3 тижні.
  • Коллаборативний редактор з Yjs і persistence: 4–8 тижнів.
  • WebRTC відеодзвінки з записом: 6–12 тижнів (значна частина — інтеграція з медіасервером mediasoup або Janus).

Зв'яжіться з нами для оцінки вашого проєкту. Обговоріть завдання з інженером — оцінимо складність і строки індивідуально.