Разработка · 7 мин чтения

Realtime-дашборд аналитики на Vue с WebSocket: как реализовать обновление данных

Ко мне регулярно приходят с одной и той же задачей: нужен realtime дашборд аналитики на Vue с WebSocket, чтобы продажи, заказы, показания датчиков или статусы бота обновлялись на экране без нажатия F5. За последний год собирал такие панели для интернет-магазина на WooCommerce с оплатой через T‑Bank, для мониторинга статусов доставки СДЭК и для дашборда, куда n8n сливает события из десятка источников одним потоком. В статье - рабочая схема: от подключения сокета во Vue 3 до обработки обрывов связи и оптимизации рендера, когда данных становится много.

Когда WebSocket оправдан, а когда хватит polling или SSE

Первый вопрос, который задаю на созвоне с заказчиком: как часто реально меняются данные и нужна ли обратная связь от клиента к серверу. Если менеджер смотрит на отчёт раз в пять минут, тащить WebSocket смысла нет - обычный polling с fetch раз в 30-60 секунд закроет задачу дешевле и без танцев с переподключениями.

Способ обновления Задержка Нагрузка на сервер Когда использую
Polling (setInterval + fetch) 3-60 сек Растёт с числом клиентов, каждый запрос - новое соединение Отчёты, которые смотрят периодически, нагрузка небольшая
SSE (EventSource) до 1 сек Ниже, но канал только сервер→клиент Лента уведомлений, статус заказа без ответа от клиента
WebSocket 50-200 мс Одно постоянное соединение на клиента Биржевые котировки, метрики продаж, статусы CRM/эквайринга, чаты

На практике WebSocket беру, когда события реально льются постоянно: заказы из WooCommerce с оплатой через T‑Bank, вебхуки СДЭК о смене статуса посылки, показания IoT-датчиков или события из n8n-сценариев. Если поток редкий и разовый, дешевле оставить polling и не городить лишнюю инфраструктуру.

Архитектура realtime-дашборда: от источника событий до Vue-компонентов

Схема, которую использую почти без изменений от проекта к проекту, простая: источник событий (CRM, платёжный шлюз, брокер сообщений или n8n) отправляет вебхук на бэкенд-шлюз, шлюз держит пул WebSocket-соединений и рассылает событие всем подписанным клиентам, а на фронте Vue-стор ловит сообщение и обновляет реактивные данные.

Бэкенд-шлюз пишу на FastAPI или Node с библиотекой ws - выбор зависит от того, что уже крутится в проекте. Для нагрузки от пары сотен одновременных соединений хватает голого WebSocket-сервера, для тысяч подключений добавляю Redis pub/sub, чтобы несколько инстансов сервера могли рассылать события синхронно.

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()
clients: set[WebSocket] = set()

@app.websocket("/ws/dashboard")
async def dashboard_ws(websocket: WebSocket):
    await websocket.accept()
    clients.add(websocket)
    try:
        while True:
            await websocket.receive_text()
    except WebSocketDisconnect:
        clients.discard(websocket)

async def broadcast(event: dict):
    dead = []
    for client in clients:
        try:
            await client.send_json(event)
        except Exception:
            dead.append(client)
    for client in dead:
        clients.discard(client)

Функцию broadcast вызываю из обработчика вебхука - заказ пришёл, оплата прошла, статус доставки сменился - и событие мгновенно уходит всем подключённым дашбордам.

Подключение WebSocket во Vue 3 на Composition API

На фронте выношу логику соединения в отдельный composable - так его переиспользую между виджетами дашборда без дублирования кода подключения.

import { ref, onMounted, onUnmounted } from 'vue'

export function useWebSocket(url) {
  const socket = ref(null)
  const isConnected = ref(false)
  const lastMessage = ref(null)
  let reconnectAttempts = 0

  function connect() {
    socket.value = new WebSocket(url)

    socket.value.onopen = () => {
      isConnected.value = true
      reconnectAttempts = 0
    }

    socket.value.onmessage = (event) => {
      lastMessage.value = JSON.parse(event.data)
    }

    socket.value.onclose = () => {
      isConnected.value = false
      const delay = Math.min(1000 * 2 ** reconnectAttempts, 30000)
      reconnectAttempts++
      setTimeout(connect, delay)
    }
  }

  onMounted(connect)
  onUnmounted(() => socket.value?.close())

  return { isConnected, lastMessage }
}

В компоненте дашборда composable даёт готовый реактивный поток: подписался на lastMessage, и каждое новое сообщение прогоняешь через обработчик, который раскладывает данные по нужным виджетам - графику продаж, счётчику заказов, ленте последних событий.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Обновление данных без перезагрузки: реактивность и Pinia

Сырые сообщения из сокета напрямую в компоненты не пускаю - между сокетом и UI всегда стоит Pinia-стор. Так проще тестировать логику и не тянуть WebSocket-инстанс в каждый компонент через prop drilling.

  • Стор подписывается на события через тот же composable и раскладывает payload по своим полям - orders, revenue, onlineUsers.
  • Компоненты дашборда просто читают геттеры стора и не знают, что данные приходят по сокету, а не по обычному запросу.
  • Для счётчиков и графиков использую computed с плавной анимацией числа - резкий скачок цифры на экране менеджера выглядит дёрганым и раздражает больше, чем задержка в полсекунды.

Если события летят очень часто (например, тикер сделок или логи в реальном времени), обновлять стор на каждое сообщение - плохая идея, интерфейс начинает подтормаживать. Здесь ставлю буфер: сообщения складываю в массив, а рендер запускаю раз в 100-200 мс через requestAnimationFrame или простой throttle. На дашборде для мониторинга заказов с частотой в десятки событий в секунду это снизило нагрузку на рендер примерно втрое - глазом разница не заметна, а браузер не захлёбывается.

Переподключение и устойчивость к обрывам связи

Большая часть багов в realtime-дашбордах вылезает не в момент разработки, а через неделю эксплуатации, когда у пользователя моргает Wi-Fi или ноутбук уходит в сон. Без нормальной обработки закрытия соединения дашборд просто зависает с последними полученными данными и никак об этом не сигналит.

Что закладываю обязательно:

  • Экспоненциальный backoff при переподключении (пример в composable выше) - чтобы клиент не долбил сервер запросами раз в секунду при массовом обрыве.
  • Heartbeat: сервер и клиент обмениваются ping/pong каждые 20-30 секунд, если pong не пришёл - соединение считается мёртвым и пересоздаётся, не дожидаясь системного таймаута TCP.
  • Визуальный индикатор статуса соединения в углу дашборда - простая точка «онлайн/переподключение», которая экономит заказчику десятки вопросов в поддержку.
  • При восстановлении соединения - досинхронизация: клиент запрашивает снапшот состояния через обычный REST-запрос, чтобы не потерять события, случившиеся во время разрыва.

Готовый шаблон с этой логикой я не переписываю с нуля на каждом проекте - держу под рукой готовый скрипт переподключения WebSocket-соединения и адаптирую его под конкретный бэкенд.

Производительность: дашборд не должен тормозить при потоке обновлений

Когда событий становится действительно много - сотни в секунду на активном интернет-магазине в сезон распродаж - узкое место обычно не сеть, а рендер DOM. Что помогает на практике:

  • Батчинг обновлений стора вместо реакции на каждое сообщение отдельно - собираю пачку за тик и применяю разом.
  • Виртуализация списков для лент событий длиннее 50-100 строк (vue-virtual-scroller закрывает это почти без доработок).
  • Тяжёлые агрегации (пересчёт сумм, группировка по категориям) выношу в Web Worker, чтобы не блокировать основной поток и анимации.
  • Графики обновляю не на каждое сообщение, а раз в 1-2 секунды - глазу этого достаточно, а нагрузка на Canvas/SVG падает кратно.

На одном из проектов с дашбордом статусов доставки СДЭК для 20+ складов именно батчинг и виртуализация списка убрали фризы интерфейса при пиковой нагрузке - до этого вкладка браузера начинала подвисать уже на паре сотен активных заказов на экране одновременно.

Сроки и стоимость разработки realtime-дашборда

Цена зависит от того, есть ли уже готовый фронт и бэкенд-инфраструктура или дашборд собирается с нуля.

Задача Цена Срок
Дашборд/BI на Vue.js с нуля (фронт + WebSocket-шлюз) от 90 000 ₽ 3-4 недели
Подключение WebSocket к готовому фронту (интеграция с CRM, эквайрингом, СДЭК) от 40 000 ₽ от 1 недели
Автоматизация сбора событий в n8n перед шлюзом от 25 000 ₽ от 3-5 дней
Техподдержка после запуска от 15 000 ₽/мес -

Для сравнения: в студиях и у фрилансеров на бирже такой дашборд с нуля обычно оценивают в 150 000-400 000 ₽ - цена растёт из-за дополнительных слоёв согласования и часто избыточной архитектуры под задачи, которые реально решаются проще. Если события в дашборд уже нужно забирать из телеграм-бота на aiogram или из потока платежей, закладываю это отдельной строкой - интеграция с внешним источником обычно занимает 3-7 дней сверх базовой сборки.

Разработка дашборда на Vue.js в моём случае всегда начинается с созвона, на котором прикидываем реальный объём событий в секунду - от этого зависит, нужен ли Redis pub/sub сразу или можно обойтись одним инстансом сервера.

Интерфейс аналитики и отчётов

Дашборд / BI

от 90 000 ₽

Подробнее →

Частые вопросы

Чем WebSocket лучше polling для дашборда аналитики?

WebSocket держит одно постоянное соединение и доставляет событие за 50-200 мс, тогда как polling опрашивает сервер по расписанию и либо создаёт лишнюю нагрузку частыми запросами, либо показывает устаревшие данные при редком опросе. Для дашбордов с потоком заказов, платежей или статусов доставки разница ощущается уже при нескольких десятках событий в час.

Можно ли подключить WebSocket к готовому дашборду на Vue без переписывания фронта?

Да, если компоненты уже читают данные из стора (Pinia или Vuex), обычно достаточно добавить composable с подключением к сокету и завести обновление в существующие геттеры - переписывать вёрстку и логику отображения не требуется. Такая доработка чаще всего укладывается в рамки комплексной интеграции от 40 000 ₽.

Как дашборд ведёт себя при потере интернета у пользователя?

При правильной реализации - восстанавливается сам: клиент ловит закрытие сокета, ждёт по экспоненциальной задержке и переподключается, а после восстановления связи подтягивает актуальный снапшот данных через обычный REST-запрос, чтобы не потерять события, случившиеся во время разрыва. Без этой логики дашборд просто замирает на последних полученных данных и не подсказывает, что связь потеряна.

Сколько стоит и сколько по времени делается realtime-дашборд на Vue?

Сборка с нуля - от 90 000 ₽ и 3-4 недели, включая фронт, WebSocket-шлюз и базовую логику переподключения. Если нужно только подключить сокет к уже готовому интерфейсу или добавить один источник данных вроде СДЭК или T‑Bank, срок и цена меньше - от недели и от 40 000 ₽ соответственно.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно