Согласие на cookie для Next.js, Gatsby и статических сайтов: руководство по интеграции для разработчиков
Проблема согласия на статических сайтах
Современные JavaScript-фреймворки, такие как Next.js, Gatsby и Nuxt.js, привнесли смену парадигмы в способ создания и доставки веб-страниц. Страницы предварительно рендерятся во время сборки или на сервере, а затем гидратируются на клиенте. Это создаёт уникальную проблему для согласия на cookie: баннер согласия должен быть готов до выполнения любых скриптов отслеживания, но сама страница может быть уже отрендерена и закэширована на границе сети.
Традиционные CMP были разработаны для серверно рендеримых PHP или простых HTML-страниц, где документ загружается линейно сверху вниз. В мире фреймворков с разделением кода, ленивой загрузкой и потоковым серверным рендерингом эти предположения нарушаются. Чтобы правильно настроить согласие в этих средах, нужно понимать конвейер рендеринга.
Почему тайминг важнее, чем вы думаете
На стандартной HTML-странице размещение скрипта CMP в <head> перед другими скриптами просто. В Next.js App Router или Gatsby ситуация сложнее:
- Предварительно отрендеренный HTML приходит первым: браузер получает полный HTML с CDN или сервера. Если в этот HTML встроены какие-либо инлайн-скрипты или сторонние теги, они могут выполниться до загрузки вашей логики согласия.
- Разрыв гидратации: гидратация React происходит после отрисовки HTML. Если ваш компонент согласия является компонентом React, он не существует в функциональном состоянии до завершения гидратации. В течение этого разрыва теги Google или скрипты аналитики могут сработать без согласия.
- Сложности с кэшированием на границе: если вы используете ISR (инкрементальную статическую регенерацию) или граничные функции, HTML кэшируется. Вы не можете динамически внедрять зависящую от согласия логику в кэшированный HTML без клиентского механизма.
Основной принцип таков: согласие должно устанавливаться на уровне скрипта, а не на уровне компонента. Компонент React, который рендерит баннер согласия, слишком запаздывает, если становится интерактивным только после гидратации.
Интеграция с Next.js App Router
Next.js 13+ с App Router привнёс новый способ обработки скриптов. Вот рекомендуемый подход для интеграции согласия:
Шаг 1: загрузите скрипт CMP в корневом макете
Используйте компонент Next.js Script со стратегией beforeInteractive в вашем корневом layout.tsx. Это указывает Next.js внедрить скрипт в исходный HTML-документ до начала гидратации:
Стратегия beforeInteractive критически важна. Стратегия по умолчанию afterInteractive загружает скрипты после гидратации, что слишком поздно для согласия. При beforeInteractive скрипт CMP включается в серверно рендеримый HTML и выполняется при загрузке страницы.
Шаг 2: установите согласие по умолчанию перед тегами Google
Перед вашим сниппетом Google Tag Manager или gtag.js включите инлайн-скрипт, который устанавливает состояния согласия по умолчанию. Это гарантирует, что даже если GTM загрузится до появления баннера CMP, он будет уважать настройки отказа по умолчанию:
Этот инлайн-скрипт следует разместить в <head> вашего корневого макета, перед скриптами CMP и GTM. В Next.js для этой цели вы можете использовать обычный тег <script> внутри элемента <head> вашего макета.
Шаг 3: обрабатывайте изменения маршрута
В навигации одностраничного приложения скрипт CMP загружается один раз, но изменения маршрута не вызывают полную перезагрузку страницы. Ваша CMP должна сохраняться при навигации на стороне клиента. FlexyConsent обрабатывает это автоматически — после загрузки она остаётся активной при всех изменениях маршрута без повторной инициализации.
Интеграция с Next.js Pages Router
Для проектов, всё ещё использующих Pages Router, подход аналогичен, но использует _document.tsx вместо корневого макета. Разместите скрипт CMP в компоненте <Head> вашего пользовательского класса Document. Стратегия beforeInteractive работает таким же образом в Pages Router.
Ключевое отличие в том, что _document.tsx рендерится только на сервере, поэтому любая логика согласия здесь гарантированно находится в исходной полезной нагрузке HTML.
Интеграция со статическим сайтом Gatsby
Gatsby генерирует полностью статический HTML во время сборки. Серверного рендеринга во время запроса нет, что упрощает одни аспекты, но усложняет другие:
- Используйте
gatsby-ssr.tsxдля внедрения скрипта CMP в<head>каждой страницы. APIonRenderBodyпозволяет добавлять скрипты в head, которые будут присутствовать в каждом статическом HTML-файле. - Избегайте плагинов Gatsby, которые лениво загружают согласие: некоторые сообществ-плагины оборачивают согласие в компоненты React, которые монтируются только после гидратации. Это создаёт ранее обсуждённый разрыв тайминга.
- Размещайте настройки согласия по умолчанию инлайн: используйте
setHeadComponentsвgatsby-ssr.tsx, чтобы добавить инлайн-скрипт, устанавливающий состояния согласия по умолчанию. Этот скрипт будет в статическом HTML и выполнится немедленно.
Подход Gatsby на основе времени сборки означает, что каждый HTML-файл на вашем CDN будет включать скрипт согласия. Это на самом деле идеально — нет серверной логики, которая может дать сбой или неправильно закэшироваться.
Соображения по Nuxt.js
Nuxt.js (на основе Vue) имеет свои собственные паттерны. В Nuxt 3 используйте composable useHead или конфигурацию app head в nuxt.config.ts, чтобы добавить скрипт CMP глобально. Nuxt поддерживает опцию body: false (которая размещает скрипты в head) и атрибут async для неблокирующей загрузки.
Для режима серверного рендеринга Nuxt применяется тот же принцип: скрипт CMP должен быть в исходном HTML-ответе, а не динамически внедряться компонентом Vue после монтирования.
Избегание сдвига макета
Баннеры согласия печально известны тем, что вызывают Cumulative Layout Shift (CLS) — Core Web Vital, влияющий на SEO-рейтинги. Когда баннер всплывает после рендеринга страницы, он сдвигает контент вниз или неожиданно накладывается на него.
Стратегии минимизации CLS от баннеров согласия:
- Используйте баннер с нижним позиционированием: баннеры внизу области просмотра не сдвигают контент страницы. Это наиболее благоприятный для CLS подход.
- Зарезервируйте место: если вы должны использовать верхний баннер, зарезервируйте вертикальное пространство в вашем CSS, чтобы макет страницы учитывал баннер до его рендеринга.
- Избегайте модальных наложений при загрузке: полноэкранные стены согласия, которые появляются после рендеринга страницы, вызывают воспринимаемую нестабильность макета. Если вам нужна стена, рендерите её как часть исходного состояния страницы.
- Загружайте CMP синхронно в head: когда CMP загружается как блокирующий рендеринг скрипт в head, баннер может появиться как часть исходной отрисовки, а не всплывать позже.
Фреймворк-независимый подход FlexyConsent
FlexyConsent был разработан для работы с любым фреймворком — или вообще без фреймворка — за счёт работы на уровне скрипта, а не на уровне компонента. Вот почему это важно:
- Один асинхронный тег скрипта: один тег
<script>в<head>— это всё, что нужно. Нет npm-пакетов для установки, нет фреймворк-специфичных обёрток, нет конфигурации сборки. - Настройки согласия по умолчанию срабатывают немедленно: скрипт устанавливает настройки по умолчанию Consent Mode V2 в качестве своего первого действия, до любого колбэка или манипуляции с DOM. Это означает, что теги Google уважают согласие с самой первой миллисекунды.
- Нет зависимости от DOM: логика согласия не ждёт гидратации React, Vue или Svelte. Она работает независимо от жизненного цикла фреймворка.
- Работает с SSG, SSR, ISR и CSR: поскольку это обычный скрипт, он функционирует идентично, независимо от того, была ли страница статически сгенерирована, серверно отрендерена, инкрементально регенерирована или отрендерена на стороне клиента.
Совет разработчику: самый простой тест правильной интеграции CMP — открыть вкладку Network в браузере, отфильтровать по доменам Google и перезагрузить страницу. Никакие запросы Google не должны срабатывать до появления команды согласия по умолчанию в консоли. Если они срабатывают, ваша CMP загружается слишком поздно.
Бесплатный план FlexyConsent поддерживает неограниченные просмотры страниц и работает с Next.js, Gatsby, Nuxt, Astro, SvelteKit, Remix и обычным HTML. Интеграция одинакова для всех них: один тег скрипта, правильно размещённый.