Назад к блогу

Добавление аналитики с приоритетом конфиденциальности на сайт Astro

View Transitions в Astro молча ломают стандартные скрипты аналитики. Вот правильный шаблон для отслеживания каждого изменения маршрута без cookie или баннера согласия.

View Transitions в Astro подменяют содержимое страницы на месте без полной перезагрузки браузера — это означает, что любой скрипт аналитики, полагающийся на DOMContentLoaded или load, сработает только один раз, при начальном посещении. Каждая последующая клиентская навигация остаётся невидимой. Это не ошибка конфигурации с вашей стороны; это фундаментальное следствие того, как работает ClientRouter.

Та же проблема молчаливого отслеживания существует в SvelteKit 2 и Remix, но решение в каждом фреймворке своё. В Astro ответом является событие жизненного цикла astro:page-load.

Почему обычный тег script не работает

Когда <ClientRouter /> активен (включается через astro:transitions в Astro 4, по умолчанию в Astro 5), навигация не выгружает страницу. Astro загружает следующую страницу в фоновом режиме, подменяет DOM и обновляет URL — всё это без уничтожения текущего контекста JavaScript.

Скрипт трекера, загруженный через обычный тег <script>, обрабатывается конвейером сборки Astro как связанный модульный скрипт. Связанные модульные скрипты выполняются ровно один раз за сессию страницы. Трекер инициализируется, фиксирует просмотр страницы для целевой страницы, а затем никогда больше не запускается, когда пользователь перемещается по сайту.

Чтобы срабатывать при каждой навигации, логика инициализации должна быть подключена к жизненному циклу навигации Astro. Правильный хук — astro:page-load, который срабатывает после того, как новая страница становится видна и все блокирующие скрипты загружены — как при начальной загрузке, так и при каждом последующем переходе.

Правильная интеграция

Добавьте скрипт трекера один раз в ваш корневой макет (src/layouts/Layout.astro или эквивалент), затем прикрепите слушатель для astro:page-load:

---
// Layout.astro
---
<html lang="en">
  <head>
    <!-- your head content -->
  </head>
  <body>
    <slot />

    <script
      is:inline
      src="https://api.monoid.website/tracker.min.js"
      data-site-id="YOUR_SITE_ID"
      async
    ></script>
  </body>
</html>

Директива is:inline сообщает сборщику Astro оставить этот скрипт ровно таким, какой он есть. Без неё Astro обработал бы скрипт как модуль, дедуплицировал бы его и подавил повторное выполнение. С is:inline тег скрипта выводится дословно в HTML-выводе каждой страницы.

Для сайтов, не использующих View Transitions, этого достаточно. Трекер срабатывает один раз при загрузке, и вы закончили.

Обработка навигации View Transitions

Если ваш сайт использует <ClientRouter />, встроенный хук history.pushState трекера срабатывает правильно после каждого перехода — трекер внутренне слушает события навигации Astro. Дополнительная настройка не требуется.

Чтобы проверить, откройте вкладку Network в DevTools и отфильтруйте по collect. Перейдите между двумя страницами. Вы должны увидеть один POST-запрос на каждую навигацию, включая первую загрузку.

Если вы внедряете трекер условно и вам нужно, чтобы он повторно инициализировался после каждой подмены (например, после монтирования динамического острова), вы можете вручную запустить повторный запуск:

<script data-astro-rerun>
  // This block re-executes after every View Transition.
  // Use sparingly — most tracker logic should not live here.
  if (window.__monoid) {
    window.__monoid.trackPageview();
  }
</script>

Атрибут data-astro-rerun принудительно заставляет встроенные скрипты выполняться повторно после каждого перехода. Обратите внимание, что он подразумевает is:inline, поэтому применяется только к несвязанным скриптам.

Разделение окружений

Проекты Astro обычно имеют локальную разработку, предпросмотр и продакшен. Используйте переменную окружения для хранения site_id и подавления отслеживания в разработке:

---
const siteId = import.meta.env.PUBLIC_MONOID_SITE_ID
---

{siteId && (
  <script
    is:inline
    src="https://api.monoid.website/tracker.min.js"
    data-site-id={siteId}
    async
  ></script>
)}

Установите PUBLIC_MONOID_SITE_ID в .env для продакшена и оставьте её неустановленной локально. Astro предоставляет клиенту переменные с префиксом PUBLIC_; серверные переменные (без префикса) не видны в коде, выполняемом в браузере.

Что вам не нужно

Никакого баннера согласия на cookie. Никакой интеграции CMP. Конечная точка сбора вычисляет ежедневный хэш посетителя из IP-адреса, User-Agent, серверного секрета и текущей даты:

visitor_hash = SHA-256(IP + UA + SALT_SECRET + YYYY-MM-DD)

Хэш сбрасывается каждые 24 часа. На устройстве посетителя ничего не сохраняется. Общее семейство браузеров и тип устройства определяются на стороне сервера из User-Agent запроса для агрегированной отчётности — полные строки User-Agent, версии браузеров и постоянные идентификаторы никогда не сохраняются.

Сайты Astro часто полностью статичны или отрендерены на краю с минимальным JavaScript. Добавление скрипта аналитики менее 2 КБ без cookie и без требования согласия естественно вписывается в эту философию. Не нужно поддерживать npm-пакет, обновлять SDK или документировать правовое основание GDPR для обработки аналитики.