Consentl z Next.js

Umieść Consentl jako pierwszy w <head> głównego layoutu App Router, ładuj inne tagi przez mały komponent ConsentlScript lub użyj _document w Pages Router.

Skrypt ładujący jako pierwszy w <head>
Tak

Na potrzeby wstępnego blokowania i domyślnych wartości Google Consent Mode Consentl musi uruchamiać się przed każdym innym tagiem, synchronicznie (bez async, defer ani type="module"). W Next.js oznacza to zwykły tag <script>, a nie next/script.

<script src="https://api.consentl.com/v1/loader.js?k=SITE_KEY&l=LANG"></script>

Klucz witryny należy przechowywać w zmiennej środowiskowej, np. NEXT_PUBLIC_CONSENTL_SITE_KEY. Klucz jest publiczny, więc udostępnienie go przeglądarce nie stanowi problemu. Consent Mode wymaga planu Pro lub wyższego, a automatyczne blokowanie znanych narzędzi śledzących – planu Business.

App Router (app/layout.tsx)

Przetestowano z Next.js 16.4 i React 19.

Co React 19 i Next robią z tagami

  • React traktuje każdy <script async src> jako zasób i przenosi go na początek <head>, powyżej zwykłego synchronicznego skryptu, nawet jeżeli zapisano go po Consentl. Własne skrypty Next również znajdują się na początku.
  • next/script ze strategy="afterInteractive" (oraz beforeInteractive) zapisuje na stronie <link rel="preload"> dla tagu. Przeglądarka pobiera tag podczas odczytywania strony, przed wyrażeniem zgody. Consentl uniemożliwia jego uruchomienie, ale nie to żądanie.

Strumieniowane dane strony Next (self.__next_f) nigdy nie są wstrzymywane przez Consentl, niezależnie od ich zawartości.

  1. Consentl jako zwykły <script src>, pierwszy w <head>. React go nie przenosi, występuje na stronie raz, a odwiedzający zawsze otrzymują aktualne ustawienia banera (skrypt ładujący jest przechowywany w pamięci podręcznej przez 5 minut).
  2. Każdy inny tag, który musi czekać na zgodę (Google Analytics, Meta, TikTok…), przez mały komponent ConsentlScript poniżej. Zapisuje on tag jako type="text/plain" z jego kategorią Consentl: React go nie przenosi, przeglądarka go nie pobiera, a Consentl uruchamia go raz po wyrażeniu zgody, w każdym planie, również po nawigacji po stronie klienta.
  3. Można też zarządzać tymi tagami w Google Tag Manager (zob. Google Tag Manager).

Tagi Google w trybie zaawansowanym (advanced) Consent Mode (plan Pro i wyższe) z założenia ładują się przed wyrażeniem zgody; w tym trybie należy ładować je za pomocą next/script i afterInteractive. strategy="lazyOnload" nie zapisuje preload (Next 16.4) i jest wstrzymywane do czasu wyrażenia zgody, ale tylko w przypadku tagów rozpoznawanych przez automatyczne blokowanie (plan Business), dlatego lepiej używać ConsentlScript.

// app/consentl-script.tsx — works in Server and Client Components
type Category = "preferences" | "analytics" | "marketing";

/** A tag that waits for consent: inert in the page, run by Consentl once the category is accepted. */
export function ConsentlScript({ category, src, nonce, children }: { category: Category; src?: string; nonce?: string; children?: string }) {
  return src ? (
    // eslint-disable-next-line @next/next/no-sync-scripts -- type="text/plain": never loaded by the browser itself
    <script type="text/plain" data-consentl={category} src={src} nonce={nonce} />
  ) : (
    <script type="text/plain" data-consentl={category} nonce={nonce} dangerouslySetInnerHTML={{ __html: children ?? "" }} />
  );
}
// app/layout.tsx (or app/[locale]/layout.tsx)
import { ConsentlScript } from "./consentl-script";

const SITE_KEY = process.env.NEXT_PUBLIC_CONSENTL_SITE_KEY!;

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const lang = "en"; // e.g. (await params).locale
  const loader = `https://api.consentl.com/v1/loader.js?k=${encodeURIComponent(SITE_KEY)}&l=${encodeURIComponent(lang)}`;
  return (
    <html lang={lang}>
      <head>
        {/* eslint-disable-next-line @next/next/no-sync-scripts -- must block: consent defaults + prior blocking */}
        <script src={loader} />
      </head>
      <body>
        {children}
        <ConsentlScript category="analytics" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX" />
        <ConsentlScript category="analytics">
          {`window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','G-XXXXXXX');`}
        </ConsentlScript>
        <footer>
          <a href="#consentl" data-consentl-open="">Cookie settings</a>
        </footer>
      </body>
    </html>
  );
}

Przy Content Security Policy należy przekazać nonce do ConsentlScript (nonce={nonce}); Consentl zachowuje go, uruchamiając tag.

Dlaczego nie wstawiać skryptu ładującego bezpośrednio w kod strony?

Wcześniejsza wersja tej strony pobierała skrypt ładujący na serwerze i wstawiała go bezpośrednio w kod strony (inline). W App Router powoduje to wysyłanie skryptu ładującego dwukrotnie przy każdym pełnym załadowaniu strony, ponieważ React kopiuje również zawartość <script> komponentu serwerowego do danych strony, a strony budowane statycznie zachowują kopię pobraną podczas budowania do czasu ich ponownej walidacji. Na tej samej stronie testowej:

Konfiguracja HTML strony Skrypt ładujący Consentl
Zwykły tag script (zalecane) 2,2 KB po kompresji gzip 9,5 KB po kompresji gzip, jedno żądanie, w pamięci podręcznej przez 5 minut i współdzielony przez wszystkie strony
Skrypt ładujący inline 15,2 KB po kompresji gzip, przy każdym pełnym załadowaniu strony dwukrotnie na stronie

Zwykły tag kosztuje jedno żądanie do api.consentl.com przed wyrenderowaniem strony. Gdy każdy inny tag znajduje się w ConsentlScript (lub w Google Tag Manager), na stronie nie ma niczego, co przeglądarka mogłaby pobrać przed uruchomieniem Consentl.

next/script ze strategy="beforeInteractive" jest emitowany jako preload i wpis w kolejce inline; Next wstawia właściwy <script> przed hydratacją, po skryptach inline i async, które już są w HTML wyrenderowanym na serwerze.

Pages Router (pages/_document.tsx)

W Pages Router beforeInteractive jest renderowany z defer po linkach CSS. Zamiast tego należy użyć zwykłego skryptu:

import { Head, Html, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        {/* eslint-disable-next-line @next/next/no-sync-scripts */}
        <script src={`https://api.consentl.com/v1/loader.js?k=${process.env.NEXT_PUBLIC_CONSENTL_SITE_KEY}&l=en`} />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Nawigacja po stronie klienta

Nie trzeba niczego dodawać: środowisko uruchomieniowe pozostaje załadowane, wybór jest przechowywany we własnym pliku cookie consentl, a linki „Ustawienia plików cookie” działają na każdej trasie. We własnym kodzie należy sprawdzać zgodę za pomocą window.Consentl?.allowed("analytics") i zdarzenia consentl:change, a funkcje uruchamiać tylko raz (zob. Każda strona internetowa).

Sprawdzenie działania

Na stronie należy użyć View Source (a nie inspektora elementów): po własnych skryptach Next /_next/static/chunks/… Consentl musi znajdować się przed każdym innym skryptem, nie ma <link rel="preload"> dla tagu śledzącego, a Państwa tagi mają type="text/plain". Następnie należy otworzyć witrynę w oknie prywatnym i sprawdzić baner oraz kartę Network zgodnie z opisem w poradniku Każda strona internetowa: brak żądań do narzędzi śledzących przed akceptacją.

Ograniczenia

  • Tagi async zapisane w layoucie są przenoszone przez React powyżej Consentl; zamiast nich należy używać ConsentlScript.
  • next/script z afterInteractive powoduje pobranie tagu przez przeglądarkę przed wyrażeniem zgody.
  • Przy beforeInteractive skrypty, które już są w HTML wyrenderowanym na serwerze, uruchamiają się przed Consentl.
  • Zmiana języka po stronie klienta nie przeładowuje banera; nowy język obowiązuje od następnego pełnego załadowania strony.
  • Jeżeli wysyłają Państwo nagłówek Content Security Policy, należy zezwolić na https://api.consentl.com (zob. Każda strona internetowa).

Potrzebują Państwo pomocy? Prosimy pisać na adres support@consentl.com — odpowiadamy po angielsku w dni robocze.