Consentimiento de cookies para Next.js, Gatsby y sitios estáticos: guía de integración para desarrolladores
El problema del consentimiento en sitios estáticos
Los frameworks modernos de JavaScript como Next.js, Gatsby y Nuxt.js introdujeron un cambio de paradigma en cómo se construyen y entregan las páginas web. Las páginas se prerenderizan en tiempo de compilación o en el servidor y luego se hidratan en el cliente. Esto crea un desafío único para el consentimiento de cookies: el banner de consentimiento debe estar listo antes de que se ejecute cualquier script de seguimiento, pero la propia página puede estar ya renderizada y en caché en el edge.
Los CMP tradicionales se diseñaron para páginas renderizadas en servidor con PHP o HTML simple, donde el documento se carga linealmente de arriba abajo. En un mundo de frameworks con code splitting, carga diferida (lazy loading) y renderizado en servidor por streaming, esas suposiciones dejan de cumplirse. Hacer bien el consentimiento en estos entornos requiere entender el pipeline de renderizado.
Por qué el momento importa más de lo que crees
En una página HTML estándar, colocar un script de CMP en el <head> antes de otros scripts es sencillo. En Next.js App Router o Gatsby, la situación es más compleja:
- El HTML prerenderizado llega primero: El navegador recibe HTML completo desde el CDN o el servidor. Si hay scripts inline o etiquetas de terceros incrustadas en ese HTML, pueden ejecutarse antes de que cargue tu lógica de consentimiento.
- Brecha de hidratación: La hidratación de React ocurre después de que el HTML se pinta. Si tu componente de consentimiento es un componente de React, no existe en un estado funcional hasta que la hidratación se completa. Durante esta brecha, las etiquetas de Google o los scripts de analítica podrían dispararse sin consentimiento.
- Complicaciones por caché en el edge: Si usas ISR (Incremental Static Regeneration) o funciones en el edge, el HTML se almacena en caché. No puedes inyectar dinámicamente lógica dependiente del consentimiento en HTML en caché sin un mecanismo del lado del cliente.
El principio central es este: el consentimiento debe establecerse a nivel de script, no a nivel de componente. Un componente de React que renderiza un banner de consentimiento llega demasiado tarde si solo se vuelve interactivo después de la hidratación.
Integración con Next.js App Router
Next.js 13+ con App Router introdujo una nueva forma de manejar scripts. Este es el enfoque recomendado para la integración de consentimiento:
Paso 1: Cargar el script del CMP en el layout raíz
Usa el componente Script de Next.js con la estrategia beforeInteractive en tu layout.tsx raíz. Esto le indica a Next.js que inyecte el script en el documento HTML inicial, antes de que comience la hidratación:
La estrategia beforeInteractive es crítica. La estrategia predeterminada afterInteractive carga los scripts después de la hidratación, lo cual es demasiado tarde para el consentimiento. Con beforeInteractive, el script del CMP se incluye en el HTML renderizado en servidor y se ejecuta mientras la página se carga.
Paso 2: Establecer el consentimiento predeterminado antes de las etiquetas de Google
Antes de tu snippet de Google Tag Manager o gtag.js, incluye un script inline que establezca los estados de consentimiento predeterminados. Esto garantiza que, incluso si GTM se carga antes de que aparezca el banner del CMP, respete los valores denegados por defecto:
Este script inline debe colocarse en el <head> de tu layout raíz, antes de los scripts del CMP y de GTM. En Next.js, puedes usar una etiqueta <script> normal dentro del elemento <head> de tu layout para este propósito.
Paso 3: Manejar los cambios de ruta
En la navegación de una single-page application, el script del CMP se carga una vez, pero los cambios de ruta no desencadenan una recarga completa de la página. Tu CMP debe persistir a través de las navegaciones del lado del cliente. FlexyConsent maneja esto automáticamente: una vez cargado, permanece activo en todos los cambios de ruta sin necesidad de reinicialización.
Integración con Next.js Pages Router
Para proyectos que aún usan Pages Router, el enfoque es similar pero utiliza _document.tsx en lugar del layout raíz. Coloca el script del CMP en el componente <Head> de tu clase Document personalizada. La estrategia beforeInteractive funciona de la misma forma en Pages Router.
La diferencia clave es que _document.tsx solo se renderiza en el servidor, por lo que cualquier lógica de consentimiento aquí está garantizada en el payload HTML inicial.
Integración de sitios estáticos con Gatsby
Gatsby genera HTML completamente estático en tiempo de compilación. No hay renderizado en servidor en tiempo de petición, lo que simplifica algunos aspectos pero complica otros:
- Usa
gatsby-ssr.tsxpara inyectar el script del CMP en el<head>de cada página. La APIonRenderBodyte permite añadir scripts al head que estarán presentes en cada archivo HTML estático. - Evita plugins de Gatsby que cargan el consentimiento de forma diferida: Algunos plugins de la comunidad envuelven el consentimiento en componentes de React que solo se montan después de la hidratación. Esto crea la brecha de tiempo comentada antes.
- Coloca los valores de consentimiento por defecto inline: Usa
setHeadComponentsengatsby-ssr.tsxpara añadir un script inline que establezca los estados de consentimiento predeterminados. Este script estará en el HTML estático y se ejecutará de inmediato.
El enfoque de compilación de Gatsby implica que cada archivo HTML en tu CDN incluirá el script de consentimiento. Esto es en realidad ideal: no hay lógica de servidor que pueda fallar o almacenarse en caché de forma incorrecta.
Consideraciones para Nuxt.js
Nuxt.js (basado en Vue) tiene sus propios patrones. En Nuxt 3, usa el composable useHead o la configuración de app head en nuxt.config.ts para añadir el script del CMP globalmente. Nuxt admite la opción body: false (que coloca los scripts en el head) y el atributo async para una carga no bloqueante.
Para el modo de renderizado en servidor de Nuxt, se aplica el mismo principio: el script del CMP debe estar en la respuesta HTML inicial, no inyectado dinámicamente por un componente de Vue después del montaje.
Cómo evitar el cambio de diseño
Los banners de consentimiento son famosos por causar Cumulative Layout Shift (CLS), una métrica de Core Web Vitals que afecta al posicionamiento SEO. Cuando un banner aparece después de que la página se ha renderizado, empuja el contenido hacia abajo o lo superpone de forma inesperada.
Estrategias para minimizar el CLS provocado por banners de consentimiento:
- Usar un banner posicionado en la parte inferior: Los banners en la parte inferior del viewport no desplazan el contenido de la página. Este es el enfoque más respetuoso con el CLS.
- Reservar espacio: Si debes usar un banner superior, reserva el espacio vertical en tu CSS para que el layout de la página tenga en cuenta el banner antes de que se renderice.
- Evitar overlays modales al cargar: Los muros de consentimiento a pantalla completa que aparecen después de que la página se ha renderizado causan una sensación de inestabilidad en el diseño. Si necesitas un muro, renderízalo como parte del estado inicial de la página.
- Cargar el CMP de forma síncrona en el head: Cuando el CMP se carga como un script bloqueante de renderizado en el head, el banner puede aparecer como parte del primer pintado en lugar de aparecer más tarde.
El enfoque agnóstico a frameworks de FlexyConsent
FlexyConsent se diseñó para funcionar con cualquier framework —o sin framework— operando a nivel de script en lugar de a nivel de componente. Esto es importante por varias razones:
- Una sola etiqueta de script async: Solo se necesita una etiqueta
<script>en el<head>. Sin paquetes npm que instalar, sin wrappers específicos de framework, sin configuración de build. - Los valores de consentimiento por defecto se disparan de inmediato: El script establece los valores predeterminados de Consent Mode V2 como primera acción, antes de cualquier callback o manipulación del DOM. Esto significa que las etiquetas de Google respetan el consentimiento desde el primer milisegundo.
- Sin dependencia del DOM: La lógica de consentimiento no espera a que React, Vue o Svelte se hidraten. Funciona de forma independiente del ciclo de vida del framework.
- Funciona con SSG, SSR, ISR y CSR: Al ser un script plano, funciona de forma idéntica tanto si la página se generó de forma estática, se renderizó en servidor, se regeneró incrementalmente o se renderizó en el cliente.
Consejo para desarrolladores: La prueba más simple para verificar una integración correcta del CMP es abrir la pestaña Network del navegador, filtrar por dominios de Google y recargar la página. No deberían dispararse peticiones a Google antes de que aparezca el comando de consentimiento por defecto en la consola. Si lo hacen, tu CMP está cargando demasiado tarde.
El plan gratuito de FlexyConsent admite páginas vistas ilimitadas y funciona con Next.js, Gatsby, Nuxt, Astro, SvelteKit, Remix y HTML plano. La integración es la misma en todos ellos: una etiqueta de script, correctamente colocada.