Аналитика воронки «клик → лид» без трекеров на Astro SSR
Услуги
- 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> — это разрыв контекста. Посетитель пятнадцать минут читал кейс про интеграцию с 1С, нажал «Обсудить» и оказался на пустой форме, где надо заново формулировать, о чём речь. Для нас это ещё и слепое пятно: какая страница рождает обращение — неизвестно. Конверсия есть, источник — нет.
Первая идея на брейншторме была амбициознее, чем нужно: держать 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 дополняет, а не заменяет.
Подход
- Подпись, а не транспорт — целостность сигнала даёт opaque-токен, а не WSS.
- Серверная запись, а не beacon — редирект надёжнее клиентского события.
- click_id вместо localStorage — воронка без cookie, работает на серверном переходе.
- Graceful degradation как принцип — нет ключей / нет гео / невалидный токен → сайт работает, аналитика молча деградирует.
- 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?
Почему корреляция идёт по click_id, а не по anonymous_id из localStorage?
Это слежка за пользователем? Как это соотносится со 152-ФЗ?
Почему отказались от persistent WebSocket из исходной идеи?
Что если HMAC-ключей нет в окружении сборки?
Похожая задача?
Расскажите контекст — подскажем, что и как делать.