Google Analytics 4, con interruptor y respetando el consentimiento

Se añade GA4 (gtag.js) con el componente oficial `@next/third-parties`, que es lo
que recomienda el doc de Next 16 y carga el mismo gtag pero tras la hidratación.

DOS interruptores:
1. `NEXT_PUBLIC_GA_ID` en el .env (no versionado). Vacío o sin poner = no se carga
   nada. Documentada en el README.
2. El consentimiento del visitante. El sitio YA tenía un banner con la categoría
   «Analíticas» que se puede rechazar: soltar gtag en el <head> lo habría
   convertido en mentira, y con visitantes en la UE, en un problema legal.

Para lo segundo se expone `useConsent(categoría)` desde CookieConsent, que además
reacciona en caliente: `writeConsent`/`revokeConsent` ya disparaban el evento
`ns:consentchange`, así que la analítica entra en cuanto se acepta y desaparece
si se revoca, sin recargar. Arranca en `false` a propósito: hasta confirmar el
consentimiento no se carga nada.

Verificado en el build: sin consentimiento no hay ni rastro de googletagmanager
en el HTML, y la condición compilada es `id && consent ? <GoogleAnalytics/> : null`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 19:49:00 +00:00
parent fbc17f2cf8
commit 94f9731b2c
6 changed files with 71 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
'use client'
import { GoogleAnalytics } from '@next/third-parties/google'
import { useConsent } from './CookieConsent'
/**
* Google Analytics 4 (gtag.js), con DOS interruptores:
*
* 1. `NEXT_PUBLIC_GA_ID` en el .env. Vacío o sin poner = analítica desactivada y
* no se carga nada. Es el interruptor del administrador.
* 2. El consentimiento del visitante. El sitio ya tiene un banner con la categoría
* «Analíticas» que se puede rechazar; cargar gtag sin mirarlo convertiría ese
* banner en mentira (y, con visitantes en la UE, en un problema legal). Solo se
* monta si la categoría está aceptada, y `useConsent` reacciona al momento: en
* cuanto se acepta entra, y si se revoca deja de renderizarse.
*
* El componente es el oficial de Next (`@next/third-parties`), que carga el mismo
* gtag.js pero después de la hidratación, para no penalizar la primera pintada.
*/
export function Analytics() {
const consent = useConsent('analytics')
const gaId = process.env.NEXT_PUBLIC_GA_ID
if (!gaId || !consent) return null
return <GoogleAnalytics gaId={gaId} />
}
+21
View File
@@ -90,6 +90,27 @@ function revokeConsent() {
showBanner()
}
/**
* ¿Ha aceptado el usuario esta categoría de cookies? Reacciona en caliente al
* banner (`writeConsent`/`revokeConsent` disparan `ns:consentchange`), así que
* quien la use se entera de aceptar o revocar sin recargar.
*
* Arranca en `false` a propósito: en el servidor no hay cookie que leer, y hasta
* que no se confirme el consentimiento NO debe cargarse nada de la categoría.
*/
export function useConsent(category: string): boolean {
const [granted, setGranted] = useState(false)
useEffect(() => {
const sync = () => setGranted(!!readConsent()?.[category])
sync()
window.addEventListener(CONSENT_EVENT, sync)
return () => window.removeEventListener(CONSENT_EVENT, sync)
}, [category])
return granted
}
// ---------------------------------------------------------------------------
// Banner de consentimiento (fijo abajo, en todo el sitio).
// ---------------------------------------------------------------------------