Ко мне регулярно приходят с одной и той же задачей: нужен 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 ₽ соответственно.