Consentl avec Next.js

Placez Consentl en premier dans <head> du layout racine (App Router), chargez les autres balises via un composant ConsentlScript, ou utilisez _document (Pages Router).

Chargeur en premier dans <head>
Oui

Pour le blocage préalable et les valeurs par défaut de Google Consent Mode, Consentl doit s’exécuter avant toutes les autres balises, de manière synchrone (pas de async, defer ni type="module"). Dans Next.js, cela signifie une balise <script> simple, et non next/script.

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

Stockez la clé du site dans une variable d’environnement telle que NEXT_PUBLIC_CONSENTL_SITE_KEY. La clé est publique : l’exposer au navigateur ne pose donc pas de problème. Consent Mode nécessite le forfait Pro ou supérieur, le blocage automatique des traceurs connus le forfait Business.

App Router (app/layout.tsx)

Testé avec Next.js 16.4 et React 19.

Ce que React 19 et Next font de vos balises

  • React traite chaque <script async src> comme une ressource et le déplace en haut de <head>, au-dessus d’un script synchrone simple, même si vous l’avez écrit après Consentl. Les scripts propres à Next passent également en premier.
  • next/script avec strategy="afterInteractive" (et beforeInteractive) écrit un <link rel="preload"> pour la balise dans la page. Le navigateur télécharge la balise pendant qu’il lit la page, avant le consentement. Consentl l’empêche de s’exécuter, mais n’empêche pas cette requête.

Les données de page diffusées en flux par Next (self.__next_f) ne sont jamais bloquées par Consentl, quel que soit leur contenu.

  1. Consentl sous forme de <script src> simple, en premier dans <head>. React ne le déplace pas, il figure une seule fois dans la page, et les visiteurs reçoivent toujours les paramètres actuels de votre bannière (le chargeur est mis en cache pendant 5 minutes).
  2. Toutes les autres balises qui doivent attendre le consentement (Google Analytics, Meta, TikTok…) via le petit composant ConsentlScript ci-dessous. Il écrit la balise en type="text/plain" avec sa catégorie Consentl : React ne la déplace pas, le navigateur ne la télécharge pas, et Consentl l’exécute une seule fois après le consentement, avec chaque forfait, y compris après une navigation côté client.
  3. Ou gérez ces balises dans Google Tag Manager (voir Google Tag Manager).

Les balises Google en mode avancé de Consent Mode (à partir du forfait Pro) sont conçues pour se charger avant le consentement ; si vous utilisez ce mode, chargez-les avec next/script et afterInteractive. strategy="lazyOnload" n’écrit pas de preload (Next 16.4) et est bloqué jusqu’au consentement, mais uniquement pour les balises que le blocage automatique reconnaît (forfait Business) : préférez donc 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>
  );
}

Avec une Content Security Policy, transmettez votre nonce à ConsentlScript (nonce={nonce}) ; Consentl le conserve lorsqu’il exécute la balise.

Pourquoi ne pas imprimer le chargeur en ligne ?

Une version antérieure de cette page récupérait le chargeur sur le serveur et l’imprimait en ligne. Dans l’App Router, cela envoie le chargeur deux fois à chaque chargement complet de page, car React copie aussi le contenu du <script> d’un Server Component dans les données de page, et les pages générées statiquement conservent la copie récupérée au moment du build jusqu’à leur revalidation. Sur la même page de test :

Configuration HTML de la page Chargeur Consentl
Balise script simple (recommandé) 2,2 Ko compressés (gzip) 9,5 Ko compressés (gzip), une requête, mis en cache pendant 5 minutes et partagé par toutes les pages
Chargeur en ligne 15,2 Ko compressés (gzip), à chaque chargement complet de page deux fois dans la page

La balise simple coûte une requête vers api.consentl.com avant le rendu de la page. Avec toutes les autres balises dans ConsentlScript (ou Google Tag Manager), il n’y a rien dans la page que le navigateur pourrait télécharger avant l’exécution de Consentl.

next/script avec strategy="beforeInteractive" est émis sous forme d’un preload et d’une entrée de file d’attente en ligne ; Next insère le véritable <script> avant l’hydratation, après les scripts en ligne et asynchrones déjà présents dans le HTML rendu côté serveur.

Pages Router (pages/_document.tsx)

Dans le Pages Router, beforeInteractive est rendu avec defer après les liens CSS. Utilisez plutôt un script simple :

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>
  );
}

Navigation côté client

Rien de plus n’est nécessaire : l’environnement d’exécution reste chargé, le choix est stocké dans le cookie propriétaire consentl, et les liens « Paramètres des cookies » fonctionnent sur toutes les routes. Pour votre propre code, vérifiez le consentement avec window.Consentl?.allowed("analytics") et l’événement consentl:change, et ne démarrez les choses qu’une seule fois (voir Tout site web).

Vérifier le fonctionnement

Utilisez Afficher le code source (et non l’inspecteur d’éléments) sur une page : après les scripts propres à Next /_next/static/chunks/…, Consentl doit précéder tous les autres scripts, il n’y a aucun <link rel="preload"> pour une balise de suivi, et vos balises sont en type="text/plain". Ouvrez ensuite le site dans une fenêtre de navigation privée et vérifiez la bannière et l’onglet Réseau comme décrit dans Tout site web : aucune requête vers un traceur avant que vous n’acceptiez.

Limites

  • Les balises async écrites dans le layout sont déplacées au-dessus de Consentl par React ; utilisez plutôt ConsentlScript.
  • next/script avec afterInteractive fait télécharger la balise par le navigateur avant le consentement.
  • Avec beforeInteractive, les scripts déjà présents dans le HTML rendu côté serveur s’exécutent avant Consentl.
  • Un changement de langue côté client ne recharge pas la bannière ; la nouvelle langue s’applique au prochain chargement complet de page.
  • Si vous envoyez une Content Security Policy, autorisez https://api.consentl.com (voir Tout site web).

Vous êtes bloqué ? Écrivez à support@consentl.com — nous répondons en anglais les jours ouvrables.