Аналитика воронки «клик → лид» без трекеров на Astro SSR

Веб Штурм (собственный продукт) Веб-разработка / Аналитика 2026-06 — наст. время
клик→лид
воронка замкнута одним идентификатором
0 трекеров
серверная аналитика на логах прокси
152-ФЗ
хеш IP, ПДн только на submit формы

Услуги

  • complex-systems
  • seo-ai-search

Стек

  • Astro SSR
  • HMAC-SHA256 opaque-token
  • GoAccess
  • Supabase Postgres
  • rehype
  • vitest

Веб Штурм — веб-разработка и аналитика. Кнопка «Обсудить» по всему сайту теряла две вещи: контекст страницы (что читал посетитель) и сам сигнал конверсии. Мы замкнули путь клик → лид одним идентификатором: opaque HMAC-токен страницы → серверный редирект /go/discuss (пишет discuss_click с хешем IP) → баннер «Обсуждаем: тема» → форма пишет lead_submit с тем же click_id. Плюс серверная аналитика поведения на логах прокси — без единого трекер-скрипта. Это наш собственный продукт на webshturm.ru.

Кейс описывает собственное внедрение Веб Штурм. Метрики качественные (характер решения), без выдуманных клиентских цифр.

Контекст

Обычная ссылка <a href="/contacts">Обсудить</a> — это разрыв контекста. Посетитель пятнадцать минут читал кейс про интеграцию с , нажал «Обсудить» и оказался на пустой форме, где надо заново формулировать, о чём речь. Для нас это ещё и слепое пятно: какая страница рождает обращение — неизвестно. Конверсия есть, источник — нет.

Первая идея на брейншторме была амбициознее, чем нужно: держать persistent WebSocket на каждой странице. Мы её отклонили (об этом ниже) и построили более простое и надёжное решение.

Какие вызовы решены

Как сохранить контекст страницы и не дать подменить статистику? Opaque HMAC-SHA256 токен на сборке кодирует slug, аудиторию (dev/biz) и тип CTA. Подпись — по keyring с kid для ротации без инвалидации старых ссылок. У токена нет TTL — это идентичность страницы, а не сессионный токен. «Opaque» означает целостность статистики: открытый параметр легко подменить, подписанный — либо валиден, либо отброшен.

Как надёжно записать событие при переходе? Серверный redirect-tracking /go/discuss?c=<токен> (Astro SSR): декод и проверка подписи, генерация click_id, серверное обогащение (хеш IP, UA, referer, гео), запись discuss_click, 302 на /contacts, отсечка ботов и prefetch. Сервер — единственное место, где гарантированно есть IP/referer/гео и где запись не зависит от клиентского JS (beacon режется ad-блокером).

Как замкнуть воронку, если на редиректе нет localStorage? Браузер не отдаёт localStorage на серверном переходе. Поэтому корреляция идёт по click_id, который сервер генерирует сам и кладёт в URL (без cookie). Тот же click_id доезжает до /contacts и далее в lead_submit. Токен c даёт агрегатную page-level статистику; click_id сшивает один клик с одним лидом — роли не путаются.

Как унифицировать семь разрозненных CTA? Одна компонента DiscussCTA (варианты card/button/link), а markdown-контент (~37 файлов) переписывается на билде rehype-плагином: все ссылки на /contacts/go/discuss?c=…. Нет ключей — плагин gracefully оставляет /contacts.

Как собирать поведение без трекер-JS? GoAccess читает access-лог обратного прокси, фильтрует по хосту сайта и раз в несколько минут регенерирует статический HTML-дашборд с --anonymize-ip (за basic-auth). Ноль клиентского кода, источник правды — серверные логи, которые ad-блокер не отрежет. Яндекс.Метрику оставили — её поведенческие факторы сильный SEO-сигнал, GoAccess дополняет, а не заменяет.

Подход

  1. Подпись, а не транспорт — целостность сигнала даёт opaque-токен, а не WSS.
  2. Серверная запись, а не beacon — редирект надёжнее клиентского события.
  3. click_id вместо localStorage — воронка без cookie, работает на серверном переходе.
  4. Graceful degradation как принцип — нет ключей / нет гео / невалидный токен → сайт работает, аналитика молча деградирует.
  5. Privacy-first — хеш IP с солью, ПДн только на submit, GoAccess с anonymize-ip, retention событий.

Оспоренное решение: почему не persistent WebSocket

  • TLS уже шифрует трафик. WSS — тот же TLS поверх WebSocket, шифрование канала не усиливает.
  • Постоянное соединение даёт больше данных, а не меньше — против минимизации 152-ФЗ и здравого смысла нагрузки.
  • Индустрия шлёт события, а не держит каналы (sendBeacon/POST компактнее).
  • Цель была сформулирована неточно — «понятно только нам» решается подписью, а не транспортом.

Результат

  • Воронка клик → лид замкнута одним click_id — видно, какая страница рождает обращение.
  • Серверная аналитика поведения работает без единого трекер-скрипта (ad-блокеры не помеха).
  • Семь разрозненных CTA сведены к одной компоненте + rehype-переписыванию контента на билде.
  • Контур приватности встроен в архитектуру: хеш IP, ПДн только на submit, anonymize-ip.
  • Отсутствие части окружения не ломает сайт — аналитика деградирует молча.

Что использовали

Astro SSR-роут (/go/discuss, prerender=false). HMAC-SHA256 opaque-токен (keyring + kid, без TTL). Единый модуль кодирования/декодирования токена. GoAccess на логах обратного прокси (--anonymize-ip, basic-auth). Supabase Postgres для событий (discuss_click, lead_submit) с хешем IP и retention. rehype-плагин для переписывания /contacts в контенте. vitest для unit/интеграционных тестов.

Что мы можем сделать у вас

Если у вас на сайте есть кнопка, которая теряет контекст и не попадает в аналитику, — мы замкнём её в измеримую воронку без слежки: подписанный контекст-токен, серверная запись событий, аналитика на логах вместо трекеров. Обсудить →

Часто задаваемые вопросы

Зачем opaque-токен страницы вместо обычного query-параметра ?from=blog?
Открытый параметр посетитель видит и может подменить, исказив статистику. Opaque HMAC-токен подписан серверным ключом: содержит slug, аудиторию и тип CTA, но читается только нами. Цель «понятно только нам» решает подпись, а не шифрование транспорта — TLS и так шифрует канал. У токена нет TTL: это идентичность страницы, а не сессионный наунс.
Почему корреляция идёт по click_id, а не по anonymous_id из localStorage?
На серверном редиректе localStorage недоступен — браузер не отдаёт его при переходе. Поэтому сервер сам генерирует click_id (uuid), кладёт его в URL и записывает с событием discuss_click. Тот же click_id уходит в lead_submit при отправке формы — так воронка клик→лид замыкается без cookie.
Это слежка за пользователем? Как это соотносится со 152-ФЗ?
Нет. Событие клика обогащается на сервере, но IP хешируется (SHA256 с солью), сырой IP нигде не хранится, гео — производное (страна/город). Персональные данные появляются только при отправке формы, где согласие уже берётся. Для first-party essential measurement собственного сайта отдельный баннер согласия не требуется.
Почему отказались от persistent WebSocket из исходной идеи?
WSS — это тот же TLS, шифрование канала он не усиливает. Постоянное соединение генерирует больше данных, а не меньше, и сложнее в эксплуатации. Задачу «сигнал понятен только нам» закрывает opaque-токен (подпись), а не выбор транспорта. Прежде чем выбирать технологию — стоит проверить, ту ли проблему она решает.
Что если HMAC-ключей нет в окружении сборки?
Graceful degradation: rehype-плагин и компонент оставляют обычную ссылку на /contacts, сайт работает как прежде, просто без записи контекста. Аналитика — вторичный слой, она не должна быть единой точкой отказа для главного действия пользователя.

Похожая задача?

Расскажите контекст — подскажем, что и как делать.