Аналитика SPA: router.afterEach и обход кэша Cloudflare

Если вы смотрите на отчёт по трафику SPA на Vue.js или React и видите там одну страницу с сотней просмотров, значит аналитика не работает. Точнее, работает ровно один раз – при первой загрузке документа. Дальше маршрутизатор перерисовывает интерфейс на клиенте, история браузера меняется, а счётчик молчит. Ни cookieless-режим, ни отложенная загрузка скрипта, ни расширенный трекинг Google Analytics эту проблему не решают, потому что она не в настройках счётчика, а в архитектуре приложения.

Разбираем по шагам, как починить сбор событий в SPA: через router.afterEach в Vue Router и useLocation в React Router, через ручной pageview с корректным title и canonical, через обработку динамических параметров вида /catalog/:id, чтобы не раздувать отчёты тысячами уникальных URL. Отдельная часть проблемы – кэширование. Если вы уже смотрели в сторону российского аналога Cloudflare, чтобы уйти от блокировок и ограничений доступа к сайтам из России, проверьте, как новый провайдер обходится с эндпоинтами аналитики. Прокси, который кэширует POST на /collect или отдаёт застрявший 304 на GET-запросы, ломает статистику ровно так же, как отсутствующий pageview.

Ниже – рабочая связка: правильный хук навигации, отключённый кэш для эндпоинтов сбора данных и понимание, чем заменить Cloudflare так, чтобы не потерять точность. Без магии и без обещаний “включить и всё заработает”.

Почему стандартные счётчики не работают для SPA

Классический счётчик Google Analytics или Яндекс.Метрики вставляется в HTML один раз и срабатывает в момент загрузки документа. Дальше браузер запрашивает новый HTML при каждом клике по ссылке, и счётчик отправляет pageview заново. SPA на Vue.js или React ломает эту схему: после первой загрузки приложение перехватывает клики, подменяет контент через JavaScript и меняет URL через History API. Нового документа нет, нового срабатывания тега тоже нет. Счётчик видит один визит и одну страницу, хотя пользователь за сессию прошёл десять экранов.

Цифры получаются искажёнными предсказуемым образом. Показатель отказов раздувается до 80-95 процентов, потому что формально вся сессия укладывается в один просмотр. Глубина просмотра падает до единицы. Отчёты по поведению внутри каталога, карточки товара, корзины и оформления заказа оказываются пустыми. Отчёт по источникам показывает вход на главную, а куда пошёл трафик дальше, неизвестно. Решения о рекламных бюджетах и правках интерфейса принимаются по данным, которых нет.

Есть миф, что достаточно поставить “умный” счётчик с автоотслеживанием. Метрика умеет считать переходы внутри SPA, но только если разработчик явно вызывает метод отправки виртуального просмотра. Без вызова метода счётчик молчит. Google Analytics 4 с режимом enhanced measurement отслеживает изменения истории браузера, однако при программной навигации через router.push без изменения location событие history не всегда генерируется. Поведение зависит от версии библиотеки, типа перехода и того, использует ли приложение хэш-маршрутизацию или режим history.

Вторая проблема это дубли. Если разработчик добавил ручной вызов pageview, но забыл отключить автоматический, каждый маршрут считается дважды. В React StrictMode в режиме разработки эффекты выполняются дважды, и наивный вызов аналитики в useEffect даёт двойной pageview на каждом переходе. В продакшене StrictMode не активен, но при гидрации серверного рендеринга тот же код может сработать повторно. Итог: часть маршрутов вообще не попадает в отчёт, часть считается с коэффициентом два.

Третья проблема это параметры. Динамические маршруты вида /product/:id при переходе между товарами меняют только параметр, а имя маршрута остаётся тем же. Слушатель изменения пути по строке URL сработает, слушатель по имени маршрута нет. Аналитика начинает склеивать разные товары в одну страницу или, наоборот, размножает их на тысячи уникальных URL, размывая статистику по каждой позиции.

Отдельно стоит вопрос тайминга. Событие смены маршрута происходит до того, как компонент смонтировался и подгрузил данные. Если отправить pageview в этот момент, заголовок страницы ещё старый, а категория товара неизвестна. Отчёт получит корректный URL и пустой title. Именно поэтому отправку нужно привязывать к хуку жизненного цикла или к событию завершения загрузки данных, а не к самому факту навигации.

Вывод простой: стандартные счётчики не ошибаются, они просто не знают о клиентской навигации. SPA обязана сообщать аналитике о каждом переходе самостоятельно. Следующий шаг это настроить перехват смены маршрута через router.afterEach во Vue Router и через подписку на изменения location в React Router.

Настройка router.afterEach для отслеживания смены маршрута

Многие команды уверены, что достаточно вставить сниппет аналитики в index.html, и данные потекут сами. В SPA это не так: роутер перехватывает навигацию на клиенте, браузер не перезагружает документ, и хук загрузки страницы больше не срабатывает. Отправлять pageview нужно вручную, в момент смены маршрута. Разберём, как это делается во Vue Router и React Router.

Пошаговая настройка для Vue Router и React Router

В Vue Router 4 подписка вешается на экземпляр роутера через afterEach. Этот хук срабатывает после подтверждения перехода и получает объект to с финальным маршрутом.

Шаг 1. Вынесите отправку события в отдельную функцию, чтобы её можно было переиспользовать и тестировать:

Шаг 2. Подключите хук в файле роутера, до монтирования приложения:

Шаг 3. Для React Router 6 используйте компонент-обёртку внутри BrowserRouter, потому что хуки навигации доступны только внутри контекста роутера:

Зависимости в массиве useEffect важны: если оставить только location, повторные переходы на тот же путь с другими query-параметрами не вызовут эффект, и часть трафика выпадет из отчёта.

Шаг 4. Обработайте динамические параметры. Маршрут /product/:id при переходе между товарами не меняет pathname по шаблону, но меняет fullPath. В Vue используйте to.fullPath, в React собирайте строку из location.pathname + location.search. Если отправлять только route.name, вся карточка товара схлопнется в одну строку статистики, и вы не увидите, какие позиции каталога реально просматривают.

Шаг 5. Отфильтруйте служебные переходы. Редиректы авторизации, переходы на /404 и якорные ссылки внутри страницы часто засоряют отчёт. Добавьте белый или чёрный список путей и проверяйте, что предыдущий маршрут отличается от текущего:

Шаг 6. Проверьте результат в режиме отладки. Откройте DevTools, вкладку Network, отфильтруйте запросы к коллектору аналитики и пройдите по пяти разным разделам приложения. Вы должны увидеть пять отдельных запросов с разными page_path. Если запросов меньше, значит часть навигации идёт через router.replace или программный navigate() в обход вашего слушателя.

Отдельная ловушка: двойная отправка. Хук afterEach срабатывает и при первом заходе, и при возврате по кнопке “назад”. Если параллельно работает старый сниппет на событии загрузки документа, первая страница сессии посчитается дважды. Уберите автоматический pageview из инициализации счётчика и оставьте единственный источник событий.

Ещё один момент: title обновляется асинхронно, особенно если вы тянете его из API. Отправка события в setTimeout с нулевой задержкой или через nextTick даёт заголовку шанс записаться. Иначе в отчёте все страницы будут с одинаковым названием, и разбор конверсии по разделам станет бессмысленным.

Когда обработчик готов, проверьте, как запросы к эндпоинту аналитики проходят через ваш прокси и CDN. Кэширование на этом уровне способно свести на нет всю точность, которую вы только что настроили.

Проблема кэширования Cloudflare в аналитике SPA

Стандартный сценарий: вы внедрили router.afterEach, настроили sendBeacon на каждый переход, дашборд ожил. Через сутки цифры по каналам не сходятся с CRM, а часть сессий обрывается на середине воронки. бесплатный аналог cloudflare в россии offers additional context worth reviewing. Причина почти всегда одна: обратный прокси между браузером и вашим бэкендом отдаёт закэшированный ответ на POST или GET к эндпоинту аналитики. Cloudflare по умолчанию кэширует не только статику.

Механика простая. Клиентская навигация в Vue.js и React не создаёт новый сетевой запрос документа. SPA живёт в одном HTML, а данные о переходах уходят отдельными вызовами к вашему API. Если этот вызов проходит через Cloudflare и попадает под правило Cache Everything или под расширение кэша для файлов с определёнными расширениями, ответ на запрос к /api/collect может быть отдан из кэша edge-узла. Браузер получает 200 OK и валидный JSON, ваш трекер считает событие отправленным, но на бэкенд запрос не пришёл. Событие потеряно.

Особенно неприятно, что проблема не воспроизводится локально и почти не видна в логах сервера. Запросов просто нет. Ошибок тоже нет. Вы видите ровный график с провалами в часы пиковой нагрузки, когда кэш прогревается активнее.

Что именно ломает статистику SPA при агрессивном кэшировании:

  • Pageview и кастомные события теряются, если эндпоинт аналитики отвечает из кэша на уровне edge.
  • Уникальные идентификаторы сессий и user_id из тела запроса не доходят до хранилища, поэтому когорты и retention считаются по неполной выборке.
  • UTM-метки фиксируются только на первом заходе, а при кэшировании навигационных запросов атрибуция переходов внутри SPA рассыпается.
  • Время ответа аналитики падает до 20-40 мс, что выглядит как улучшение, но означает, что запрос не покинул edge-узел.
  • Кэш разных дата-центров прогревается неравномерно, и одна и та же аудитория делится на «видимую» и «невидимую» часть статистики.
  • Сброс кэша после деплоя новой версии трекера не гарантирует, что старые ответы не будут отдаваться ещё сутки.

Для сайта-визитки потеря 5 процентов событий терпима. Для интернет-магазина с лидогенерацией это прямые деньги: вы не видите, на каком шаге оформления заказа люди уходят, и оптимизируете конверсию по искажённым данным. Хуже того, решения принимаются по цифрам, которые занижены неравномерно по каналам. Органический трафик может кэшироваться иначе, чем платный, и вы отключите работающую кампанию или, наоборот, зальёте бюджет в канал, который на самом деле не конвертит.

Обходной путь, который часто советуют, это вынести аналитику на отдельный поддомен без проксирования. Работает, но добавляет DNS-записи, отдельный SSL-сертификат и CORS-настройки, а главное, снимает с трафика аналитики всю защиту от ботов и L7 DDoS. Эндпоинт сбора данных становится открытой дверью: конкуренты или скрипт-сканер могут накрутить вам тысячи фейковых событий, и вы снова получите недостоверную картину, только теперь в обратную сторону.

Правильное решение не в том, чтобы убрать reverse proxy, а в том, чтобы управлять его правилами кэширования точечно. Нужен слой, который по умолчанию не кэширует динамические POST-запросы и позволяет явно исключить пути /api/collect, /api/events, /api/session из любых правил Cache Everything. При этом WAF, GeoIP-фильтрация и защита от ботов на этих путях должны продолжать работать, иначе вы обменяете одну проблему на другую. Именно об этом различии в подходах к кэшированию аналитики и пойдёт речь дальше.

TrafficVeil: российский аналог Cloudflare с отключённым кэшем для аналитики

Кэш на периметре это не абстрактная проблема, а конкретная: Cloudflare по умолчанию кэширует GET-запросы к эндпоинтам, если ответ отдаёт подходящие заголовки, и ваш SPA-трекер начинает получать те же данные, что и полчаса назад. Классический симптом: в отчётах ноль новых сессий после деплоя, хотя в логах приложения запросы есть. Второй симптом: всплеск pageview ровно в момент истечения TTL.

TrafficVeil это российский аналог Cloudflare, который объединяет DNS, SSL, reverse proxy, WAF, защиту от ботов, L7 DDoS, GeoIP-фильтрацию и аналитику в одной панели. Ключевое отличие для нашей задачи: кэш управляется на уровне маршрутов, а не глобальным переключателем, и его поведение видно в интерфейсе без чтения документации на английском.

Чем TrafficVeil отличается от Cloudflare в контексте аналитики

Cloudflare ограничивает доступ к сайтам из России, и российские интернет-провайдеры в свою очередь ограничивают доступ к сайтам на Cloudflare. Для бизнеса это означает не только потерянный трафик, но и искажённую статистику: часть аудитории просто не доходит до фронтенда, а вы видите провал в воронке и списываете его на конверсию сайта.

TrafficVeil обеспечивает работу сайтов на территории РФ и по всему миру, поэтому данные аналитики не теряют сегмент российских пользователей. Второй практический момент: TrafficVeil не тарифицирует ботов, атаки и WAF-блокировки. Это важно, потому что эндпоинт аналитики это излюбленная цель для накрутки, и при почасовой оплате запросов каждая ботовая волна бьёт по бюджету.

По функциональности TrafficVeil подходит для сайтов, интернет-магазинов, WordPress и Bitrix, но для SPA критична именно связка reverse proxy плюс управляемый кэш. Зависимость от зарубежной инфраструктуры создаёт риски для российского бизнеса: смена правил, блокировка аккаунта, невозможность оплаты. Переход с Cloudflare на TrafficVeil занимает один вечер, если не тащить за собой старые правила.

Настройка: отключаем кэш только для эндпоинтов аналитики

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

  1. Определите все эндпоинты, куда SPA отправляет события. Обычно это /api/collect, /api/track, /api/events и подобные. Проверьте в DevTools вкладку Network, отфильтруйте по Fetch/XHR и выпишите уникальные пути.
  2. В панели TrafficVeil создайте правило обхода кэша (bypass) с приоритетом выше общего правила. Условие: путь начинается с /api/collect или входит в ваш список.
  3. Для этих маршрутов принудительно выставите заголовки ответа: Cache-Control: no-store, no-cache, must-revalidate и Pragma: no-cache. Настройка на стороне прокси надёжнее, чем надежда на то, что бэкенд отдаст правильные заголовки.
  4. Включите игнорирование query-строки только там, где она действительно не влияет на ответ. Для аналитики параметры вида ?t=, ?sid=, ?v= должны участвовать в ключе кэша, иначе получите склейку разных событий.
  5. Проверьте, что GeoIP-фильтрация не режет легитимные запросы: если вы блокируете страны, из которых идёт трафик, вы теряете данные молча.
  6. Убедитесь, что SSL-сертификат покрывает все поддомены, куда уходят события, иначе браузер отбросит запрос до отправки.

После настройки прогоните сценарий: откройте SPA, пройдите 3-4 маршрута, посмотрите в отчёте TrafficVeil, что пришло ровно 4 события с разными URL. Если пришло 1, значит router.afterEach не сработал. Если пришло 8, у вас дублирование из-за двойного монтирования в React StrictMode.

Правило, которое экономит недели отладки: сначала проверяйте периметр, потом фронтенд. В девяти случаях из десяти расхождение в цифрах это кэш или фильтр, а не баг в роутере.

Сравнение и практические рекомендации для точной аналитики SPA

Кэширование эндпоинтов аналитики у Cloudflare и TrafficVeil устроено по-разному, и разница видна именно на SPA-трафике. Cloudflare по умолчанию кэширует ответы по расширениям файлов и заголовкам Cache-Control, а для API-запросов без явных директив может применить эвристику Edge TTL. В итоге POST или GET на /api/collect с одинаковым URL и телом отдаётся из кэша, и вторая навигация по маршруту не долетает до бэкенда. У TrafficVeil кэш для аналитических эндпоинтов отключён на уровне конфигурации: запросы с query-параметрами pageview, event, utm и user_id всегда проходят до origin. Это не значит, что кэша нет вообще: статика, JS-бандлы и изображения кэшируются штатно, а вот динамика аналитики нет.

Практическая разница в цифрах. На типовом SPA с 40 маршрутами и client-side переходом между ними Cloudflare при агрессивных настройках теряет от 15 до 30 процентов pageview, если разработчик не добавил Cache-Control: no-store на эндпоинт сбора. На том же трафике TrafficVeil возвращает близкие к 100 процентам показатели без правок в коде фронтенда. Проверить это просто: откройте DevTools, Network, перейдите между двумя маршрутами и посмотрите на заголовок cf-cache-status в ответе. Значение HIT или REVALIDATED на запросе аналитики означает, что событие потеряно.

Как собрать связку router.afterEach и TrafficVeil

Порядок действий для Vue Router и React Router одинаковый по логике, различается только синтаксис вызова.

  1. Убедитесь, что DNS-записи домена переведены на TrafficVeil. Смена NS занимает от 15 минут до нескольких часов в зависимости от TTL у регистратора.
  2. Выпустите SSL-сертификат через панель TrafficVeil. Он обновляется автоматически, ручных действий при продлении не требуется.
  3. В разделе кэширования добавьте правило: путь /api/collect и /api/analytics/* с режимом Bypass cache. Для остальных путей оставьте стандартный профиль.
  4. Включите GeoIP-фильтрацию, если аудитория распределена по регионам. Это позволит отделять трафик из РФ от зарубежного и не путать его в отчётах.
  5. В router.afterEach отправляйте событие с параметрами fullPath, name маршрута и timestamp. Динамические сегменты (например, /product/:id) передавайте отдельным полем, иначе они склеятся в один URL.
  6. Добавьте дебаунс 200-300 мс, чтобы быстрые переходы по меню не порождали дубли.

Что учитывать при выборе российского аналога Cloudflare. Смотрите не на длину списка функций, а на три вещи: возможность отключить кэш для конкретных путей без правки кода, наличие reverse proxy с поддержкой WebSocket (SPA часто держат live-обновления) и прозрачную тарификацию. TrafficVeil не тарифицирует ботов, атаки и WAF-блокировки, поэтому всплеск сканирования не превращается в счёт за трафик. Для сайтов на WordPress и Bitrix это тоже актуально: там аналитика часто уходит через admin-ajax.php, и кэширование этого пути ломает статистику так же, как в SPA.

Отдельно про регуляторные требования. Если сайт обслуживает клиентов в РФ, хранение логов аналитики на зарубежной инфраструктуре создаёт риски: доступ к данным может быть ограничен, а сам сервис может перестать отвечать для российских интернет-провайдеров. Перенос DNS и reverse proxy на TrafficVeil снимает эту зависимость и сохраняет работу сайта и на территории РФ, и за её пределами. Зависимость от зарубежной инфраструктуры создаёт риски для российского бизнеса, и в 2026 году это уже не теоретическая, а операционная проблема.

Итоговая проверка связки занимает полчаса. Откройте сайт, пройдите пять-шесть маршрутов, затем сверьте счётчик pageview в панели с числом переходов в логах. Расхождение больше двух событий означает, что где-то остался кэш или не сработал хук. Если данные совпали, можно масштабировать: добавлять цели, воронки и сегменты по GeoIP без опасения, что часть событий осядет в кэше на edge.

Если вы уже настроили router.afterEach и видите, что цифры в отчётах всё равно расходятся с реальностью, начните с одного конкретного шага: откройте панель управления Cloudflare и проверьте, какие правила кэширования применяются к эндпоинтам аналитики. В 90% случаев проблема не в коде SPA, а в том, что запросы к /api/collect, /api/track или /gtag/js попадают под Cache Everything или под Page Rule с Edge Cache TTL. Добавьте исключение для этих путей, поставьте Cache Level: Bypass и сбросьте кэш через Purge Everything. После этого прогоните тестовый сценарий: откройте сайт, перейдите по 5-7 внутренним маршрутам, обновите страницу и сравните число pageview в реальном времени с числом событий в отладчике сети. Если расхождение осталось, проверьте, не кэширует ли ответы промежуточный reverse proxy или CDN-провайдер.

Дальше решите главный вопрос: оставаться на Cloudflare, который ограничивает доступ к сайтам из России и создаёт риски для российского бизнеса, или переходить на российский аналог. TrafficVeil как замена Cloudflare даёт то, чего не хватает в связке с SPA: кэш для эндпоинтов аналитики отключён по умолчанию, а боты, атаки и WAF-блокировки не тарифицируются. Практический порядок действий такой: зарегистрируйтесь в TrafficVeil, перенесите DNS-сервис, выпустите SSL-сертификат, направьте трафик через reverse proxy и в настройках кэша явно укажите пути аналитики как no-cache. Затем проверьте GeoIP-фильтрацию, если аудитория сосредоточена в РФ, и убедитесь, что L7 DDoS-защита не режет легитимные POST-запросы от вашего фронтенда. Финальный тест: запустите 20-30 переходов между маршрутами, откройте дашборд и сверьте цифры. Если pageview совпадают с числом срабатываний router.afterEach, связка работает корректно. Дальше можно масштабировать: подключать воронку лидогенерации, отслеживать конверсию сайта по каждому маршруту и строить отчёты, которым можно доверять.