Consentl con Next.js

Coloque Consentl el primero en el <head> del layout raíz del App Router, cargue las demás etiquetas con ConsentlScript o use el _document del Pages Router.

Cargador el primero en el <head>
Sí

Para el bloqueo previo y los valores predeterminados de Google Consent Mode, Consentl debe ejecutarse antes que cualquier otra etiqueta, de forma síncrona (sin async, defer ni type="module"). En Next.js, eso significa una etiqueta <script> simple, no next/script.

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

Guarde la clave del sitio en una variable de entorno como NEXT_PUBLIC_CONSENTL_SITE_KEY. La clave es pública, así que no hay problema en exponerla al navegador. Consent Mode requiere el plan Pro o superior; el bloqueo automático de rastreadores conocidos, el plan Business.

App Router (app/layout.tsx)

Probado con Next.js 16.4 y React 19.

Qué hacen React 19 y Next con sus etiquetas

  • React trata cada <script async src> como un recurso y lo traslada al principio del <head>, por encima de un script síncrono simple, aunque lo haya escrito después de Consentl. Los propios scripts de Next también van primero.
  • next/script con strategy="afterInteractive" (y beforeInteractive) escribe en la página un <link rel="preload"> para la etiqueta. El navegador descarga la etiqueta mientras lee la página, antes del consentimiento. Consentl impide que se ejecute, pero no esa solicitud.

Consentl nunca retiene los datos de página transmitidos en streaming por Next (self.__next_f), sea cual sea su contenido.

  1. Consentl como <script src> simple, el primero en el <head>. React no lo traslada, aparece una sola vez en la página y los visitantes siempre reciben la configuración actual de su banner (el cargador se almacena en caché durante 5 minutos).
  2. Todas las demás etiquetas que deben esperar el consentimiento (Google Analytics, Meta, TikTok…) mediante el pequeño componente ConsentlScript de abajo. Escribe la etiqueta como type="text/plain" con su categoría de Consentl: React no la traslada, el navegador no la descarga y Consentl la ejecuta una vez después del consentimiento, en todos los planes, también tras la navegación en el lado del cliente.
  3. O gestione esas etiquetas en Google Tag Manager (consulte Google Tag Manager).

Las etiquetas de Google en el modo avanzado de Consent Mode (plan Pro y superiores) están pensadas para cargarse antes del consentimiento; si utiliza ese modo, cárguelas con next/script y afterInteractive. strategy="lazyOnload" no escribe ningún preload (Next 16.4) y se retiene hasta el consentimiento, pero solo para las etiquetas que reconoce el bloqueo automático (plan Business), así que es preferible 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>
  );
}

Con una Content Security Policy, pase su nonce a ConsentlScript (nonce={nonce}); Consentl lo conserva al ejecutar la etiqueta.

¿Por qué no imprimir el cargador en línea?

Una versión anterior de esta página obtenía el cargador en el servidor y lo imprimía en línea. En el App Router, eso envía el cargador dos veces en cada carga completa de página, porque React también copia el contenido del <script> de un Server Component en los datos de la página, y las páginas generadas de forma estática conservan la copia obtenida durante la compilación hasta que se revalidan. En la misma página de prueba:

Configuración HTML de la página Cargador de Consentl
Etiqueta script simple (recomendado) 2,2 KB comprimido con gzip 9,5 KB comprimido con gzip, una solicitud, en caché durante 5 minutos y compartido por todas las páginas
Cargador en línea 15,2 KB comprimido con gzip, en cada carga completa de página dos veces en la página

La etiqueta simple cuesta una solicitud a api.consentl.com antes de que se muestre la página. Con todas las demás etiquetas en ConsentlScript (o en Google Tag Manager), no hay nada en la página que el navegador pueda descargar antes de que se ejecute Consentl.

next/script con strategy="beforeInteractive" se emite como un preload más una entrada de cola en línea; Next inserta el <script> real antes de la hidratación, después de los scripts en línea y asíncronos que ya están en el HTML generado en el servidor.

Pages Router (pages/_document.tsx)

En el Pages Router, beforeInteractive se genera con defer después de los enlaces CSS. Utilice en su lugar 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>
  );
}

Navegación en el lado del cliente

No hace falta nada más: el runtime permanece cargado, la elección se guarda en la cookie propia consentl y los enlaces «Configuración de cookies» funcionan en cualquier ruta. Para su propio código, compruebe el consentimiento con window.Consentl?.allowed("analytics") y el evento consentl:change, e inicie las cosas una sola vez (consulte Cualquier sitio web).

Comprobar que funciona

Utilice Ver código fuente (no el inspector de elementos) en una página: después de los propios scripts /_next/static/chunks/… de Next, Consentl debe ir antes que cualquier otro script, no hay ningún <link rel="preload"> para una etiqueta de seguimiento y sus etiquetas son type="text/plain". Después, abra el sitio en una ventana privada y compruebe el banner y la pestaña Network como se describe en Cualquier sitio web: ninguna solicitud a un rastreador antes de aceptar.

Limitaciones

  • React traslada por encima de Consentl las etiquetas async escritas en el layout; utilice en su lugar ConsentlScript.
  • next/script con afterInteractive hace que el navegador descargue la etiqueta antes del consentimiento.
  • Con beforeInteractive, los scripts que ya están en el HTML generado en el servidor se ejecutan antes que Consentl.
  • Un cambio de idioma en el lado del cliente no recarga el banner; el nuevo idioma se aplica en la siguiente carga completa de página.
  • Si envía una Content Security Policy, permita https://api.consentl.com (consulte Cualquier sitio web).

¿Se ha atascado? Escriba a support@consentl.com: respondemos en inglés en días laborables.