Guia prático com receitas passo a passo para cenários comuns de implementação do
react-lgpd-consent.
Data Format: DD/MM/YYYY | Time Format: HH:mm:ss (24h) | Idioma: pt-BR
Implementar consentimento LGPD em Next.js 14/15 com App Router, garantindo SSR-safe e bloqueio de scripts até consentimento.
npm install react-lgpd-consent @mui/material @mui/icons-material @emotion/react @emotion/styled
Crie .env.local:
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
Crie app/components/ClientConsent.tsx:
'use client'
import { ConsentProvider, ConsentScriptLoader, createGoogleAnalyticsIntegration, createGoogleTagManagerIntegration } from 'react-lgpd-consent'
import { useEffect } from 'react'
const integrations = [
createGoogleAnalyticsIntegration({ measurementId: process.env.NEXT_PUBLIC_GA_ID! }),
createGoogleTagManagerIntegration({ containerId: process.env.NEXT_PUBLIC_GTM_ID! })
]
export function ClientConsent({ children }: { children: React.ReactNode }) {
return (
<ConsentProvider
categories={{
enabledCategories: ['analytics', 'marketing', 'functional']
}}
cookieOptions={{
domain: undefined, // ou '.seudominio.com.br' para subdomínios
path: '/',
maxAge: 365 * 24 * 60 * 60 // 1 ano
}}
>
<ConsentScriptLoader integrations={integrations} />
{children}
</ConsentProvider>
)
}
Atualize app/layout.tsx:
import { ClientConsent } from './components/ClientConsent'
export const metadata = {
title: 'Minha App',
description: 'Exemplo com Consent Mode v2'
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-BR">
<body>
<ClientConsent>
{children}
</ClientConsent>
</body>
</html>
)
}
npm run dev
http://localhost:3000Compartilhar consentimento entre subdomínios (ex: www.exemplo.com e blog.exemplo.com).
Configure cookieOptions.domain com o domínio principal (prefixo .):
<ConsentProvider
categories={{ enabledCategories: ['analytics', 'marketing'] }}
cookieOptions={{
domain: '.exemplo.com.br', // ← Atenção ao ponto inicial
path: '/',
maxAge: 365 * 24 * 60 * 60
}}
>
{/* ... */}
</ConsentProvider>
Se usar nome customizado, garanta consistência:
cookieOptions={{
name: 'meu_consentimento_lgpd', // mesmo nome em todos os subdomínios
domain: '.exemplo.com.br'
}}
Para domínios em produção, sempre use secure: true:
cookieOptions={{
domain: '.exemplo.com.br',
secure: true, // apenas HTTPS
sameSite: 'Lax'
}}
www.exemplo.com.br e aceite cookiesDomain=.exemplo.com.brblog.exemplo.com.br. prefix; teste em stagingLax é recomendado; evite None sem necessidadeImplementar Google Consent Mode v2 para sincronizar sinais de consentimento com GTM/GA4.
Crie um componente para inicializar gtag antes de carregar scripts:
'use client'
export function GtagConsentBootstrap() {
useEffect(() => {
if (globalThis.window) {
globalThis.window.dataLayer = globalThis.window.dataLayer ?? []
function gtag(...args: any[]) {
if (typeof globalThis.window?.dataLayer?.push === 'function') {
globalThis.window.dataLayer.push(args)
}
}
// Inicializa Consent Mode v2 com tudo negado
gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
personalization_storage: 'denied',
functionality_storage: 'denied',
security_storage: 'granted' // sempre permitido
})
}
}, [])
return null
}
Use useConsent para atualizar sinais quando o usuário consentir:
'use client'
import { useConsent } from 'react-lgpd-consent'
import { useEffect } from 'react'
export function ConsentModeSync() {
const { preferences, consented } = useConsent()
useEffect(() => {
if (!consented || !globalThis.window) return
function gtag(...args: any[]) {
globalThis.window.dataLayer = globalThis.window.dataLayer ?? []
if (typeof globalThis.window.dataLayer?.push === 'function') {
globalThis.window.dataLayer.push(args)
}
}
gtag('consent', 'update', {
ad_storage: preferences.marketing ? 'granted' : 'denied',
ad_user_data: preferences.marketing ? 'granted' : 'denied',
ad_personalization: preferences.marketing ? 'granted' : 'denied',
analytics_storage: preferences.analytics ? 'granted' : 'denied',
personalization_storage: preferences.functional ? 'granted' : 'denied',
functionality_storage: preferences.functional ? 'granted' : 'denied'
})
}, [preferences, consented])
return null
}
import { GtagConsentBootstrap } from './components/GtagConsentBootstrap'
import { ConsentModeSync } from './components/ConsentModeSync'
import { ClientConsent } from './components/ClientConsent'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-BR">
<head>
<GtagConsentBootstrap /> {/* 1️⃣ Inicializa antes de tudo */}
</head>
<body>
<ClientConsent>
<ConsentModeSync /> {/* 2️⃣ Sincroniza updates */}
{children}
</ClientConsent>
</body>
</html>
)
}
globalThis.window?.dataLayerconsent.default e consent.updateForçar usuários a reconsentir quando a política de privacidade mudar.
Use a prop cookieVersion para versionar:
<ConsentProvider
categories={{ enabledCategories: ['analytics', 'marketing'] }}
cookieVersion="2.0.0" // ← Incrementar quando política mudar
>
{/* ... */}
</ConsentProvider>
A biblioteca automaticamente invalida cookies com versão diferente:
// Versão atual no código: 2.0.0
// Cookie do usuário: version: "1.0.0"
// Resultado: banner reaparece solicitando novo consentimento
Mantenha um registro:
// versions.ts
export const CONSENT_VERSIONS = {
'1.0.0': '01/01/2024 - Política inicial',
'1.1.0': '15/03/2024 - Adicionado Hotjar',
'2.0.0': '01/12/2024 - Nova política LGPD' // ← Atual
}
export const CURRENT_VERSION = '2.0.0'
import { CURRENT_VERSION } from './versions'
<ConsentProvider cookieVersion={CURRENT_VERSION} {...props}>
{children}
</ConsentProvider>
Customize o banner para informar sobre mudanças:
<ConsentProvider
categories={{ enabledCategories: ['analytics', 'marketing'] }}
cookieVersion="2.0.0"
customTexts={{
banner: {
title: '🔄 Política de Privacidade Atualizada',
message: 'Atualizamos nossa política de privacidade. Por favor, revise suas preferências de cookies.',
acceptButton: 'Aceitar nova política',
rejectButton: 'Revisar opções'
}
}}
>
{/* ... */}
</ConsentProvider>
cookieVersion="1.0.0" e aceite cookiesversion no cookie é "1.0.0"cookieVersion="2.0.0"consentDate e lastUpdate para auditoriaImplementar CSP rigoroso permitindo apenas scripts autorizados via nonce.
Crie middleware para gerar nonce único por request:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import crypto from 'crypto'
export function middleware(request: NextRequest) {
const nonce = crypto.randomBytes(16).toString('base64')
const response = NextResponse.next()
response.headers.set(
'Content-Security-Policy',
`script-src 'self' 'nonce-${nonce}' https://www.googletagmanager.com https://www.google-analytics.com; object-src 'none'; base-uri 'self';`
)
response.headers.set('x-nonce', nonce)
return response
}
// app/layout.tsx
import { headers } from 'next/headers'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers()
const nonce = headersList.get('x-nonce') || ''
return (
<html lang="pt-BR">
<body>
<ClientConsent nonce={nonce}>
{children}
</ClientConsent>
</body>
</html>
)
}
Atualize ConsentScriptLoader para aceitar nonce:
'use client'
import { ConsentScriptLoader } from 'react-lgpd-consent'
export function ClientConsent({ nonce, children }: { nonce: string; children: React.ReactNode }) {
return (
<ConsentProvider {...}>
<ConsentScriptLoader
integrations={integrations}
scriptAttributes={{ nonce }} // ← Propaga nonce para scripts
/>
{children}
</ConsentProvider>
)
}
Verifique se o header está correto:
// app/api/csp-report/route.ts
export async function POST(request: Request) {
const report = await request.json()
console.error('CSP Violation:', report)
return new Response('OK', { status: 200 })
}
Adicione ao CSP:
Content-Security-Policy: ... report-uri /api/csp-report
nonce attributeImplementar consentimento LGPD em SPA Vite + React.
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install react-lgpd-consent @mui/material @mui/icons-material @emotion/react @emotion/styled
Crie .env:
VITE_GA_ID=G-XXXXXXXXXX
VITE_GTM_ID=GTM-XXXXXXX
Atualize src/main.tsx:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { ConsentProvider, ConsentScriptLoader, createGoogleAnalyticsIntegration } from 'react-lgpd-consent'
const integrations = [
createGoogleAnalyticsIntegration({
measurementId: import.meta.env.VITE_GA_ID
})
]
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ConsentProvider
categories={{ enabledCategories: ['analytics', 'marketing'] }}
>
<ConsentScriptLoader integrations={integrations} />
<App />
</ConsentProvider>
</React.StrictMode>
)
// src/App.tsx
import { useConsent } from 'react-lgpd-consent'
function App() {
const { consented, preferences, openPreferences } = useConsent()
return (
<div>
<h1>Minha SPA com LGPD</h1>
<p>Status: {consented ? '✅ Consentido' : '❌ Aguardando'}</p>
<button onClick={openPreferences}>Preferências</button>
</div>
)
}
export default App
npm run dev
http://localhost:5173Criar UI de consentimento personalizada usando apenas @react-lgpd-consent/core (headless).
npm install @react-lgpd-consent/core
import { ConsentProvider } from '@react-lgpd-consent/core'
function App() {
return (
<ConsentProvider
categories={{ enabledCategories: ['analytics', 'marketing'] }}
disableDefaultUI={true} // ← Desabilita UI padrão
>
<CustomBanner />
<YourApp />
</ConsentProvider>
)
}
import { useConsent } from '@react-lgpd-consent/core'
import './CustomBanner.css'
export function CustomBanner() {
const { consented, acceptAll, rejectAll, openPreferences } = useConsent()
if (consented) return null
return (
<div className="custom-banner">
<p>🍪 Este site usa cookies. Aceita?</p>
<button onClick={acceptAll}>Aceitar Tudo</button>
<button onClick={rejectAll}>Recusar</button>
<button onClick={openPreferences}>Personalizar</button>
</div>
)
}
import { useConsent, useCategories } from '@react-lgpd-consent/core'
export function CustomPreferencesModal() {
const { isPreferencesOpen, closePreferences, updatePreferences, preferences } = useConsent()
const categories = useCategories()
if (!isPreferencesOpen) return null
return (
<div className="modal-overlay">
<div className="modal">
<h2>Preferências de Cookies</h2>
{categories.map(cat => (
<label key={cat.id}>
<input
type="checkbox"
checked={preferences[cat.id] || false}
disabled={cat.essential}
onChange={(e) => updatePreferences({ [cat.id]: e.target.checked })}
/>
{cat.name}
</label>
))}
<button onClick={closePreferences}>Salvar</button>
</div>
</div>
)
}
Implementar consentimento com suporte a múltiplos idiomas (pt-BR, en-US, es-ES).
// translations.ts
export const translations = {
'pt-BR': {
banner: {
title: 'Este site usa cookies',
message: 'Usamos cookies para melhorar sua experiência. Aceita?',
acceptButton: 'Aceitar Tudo',
rejectButton: 'Recusar'
},
categories: {
necessary: { name: 'Necessários', description: 'Essenciais para funcionamento' },
analytics: { name: 'Analíticos', description: 'Análise de uso' },
marketing: { name: 'Marketing', description: 'Publicidade personalizada' }
}
},
'en-US': {
banner: {
title: 'This website uses cookies',
message: 'We use cookies to improve your experience. Accept?',
acceptButton: 'Accept All',
rejectButton: 'Reject'
},
categories: {
necessary: { name: 'Necessary', description: 'Essential for operation' },
analytics: { name: 'Analytics', description: 'Usage analysis' },
marketing: { name: 'Marketing', description: 'Personalized advertising' }
}
}
}
// useLocale.ts
import { useState, useEffect } from 'react'
export function useLocale() {
const [locale, setLocale] = useState<'pt-BR' | 'en-US'>('pt-BR')
useEffect(() => {
const browserLang = navigator.language
if (browserLang.startsWith('en')) setLocale('en-US')
else if (browserLang.startsWith('pt')) setLocale('pt-BR')
}, [])
return { locale, setLocale }
}
import { ConsentProvider } from 'react-lgpd-consent'
import { translations } from './translations'
import { useLocale } from './useLocale'
export function App() {
const { locale, setLocale } = useLocale()
const texts = translations[locale]
return (
<ConsentProvider
categories={{
enabledCategories: ['analytics', 'marketing'],
customCategories: [
{ id: 'analytics', ...texts.categories.analytics },
{ id: 'marketing', ...texts.categories.marketing }
]
}}
texts={texts}
>
<button onClick={() => setLocale(locale === 'pt-BR' ? 'en-US' : 'pt-BR')}>
{locale === 'pt-BR' ? '🇺🇸 English' : '🇧🇷 Português'}
</button>
{/* App */}
</ConsentProvider>
)
}
import { ConsentProvider, EXPANDED_DEFAULT_TEXTS } from 'react-lgpd-consent'
<ConsentProvider
categories={{ enabledCategories: ['analytics'] }}
texts={EXPANDED_DEFAULT_TEXTS}
language={locale === 'en-US' ? 'en' : 'pt'}
>
{/* App */}
</ConsentProvider>
<CookieBanner
debug
texts={{ bannerMessage: 'We use cookies', policyLink: 'Learn more' }}
language="en"
/>
Evitar falhas em Jest CJS ao importar pacotes ESM publicados como dual build.
// jest.config.cjs
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.(t|j)sx?$': ['babel-jest', { presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] }],
},
transformIgnorePatterns: ['/node_modules/(?!react-lgpd-consent|@react-lgpd-consent)/'],
}
// jest.config.cjs
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.(t|j)sx?$': ['ts-jest', { tsconfig: 'tsconfig.json', useESM: false }],
},
transformIgnorePatterns: ['/node_modules/(?!react-lgpd-consent|@react-lgpd-consent)/'],
}
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
deps: {
inline: ['react-lgpd-consent', '@react-lgpd-consent/core', '@react-lgpd-consent/mui'],
},
},
})
import { ConsentProvider } from 'react-lgpd-consent'
import { createGoogleAnalyticsIntegration } from 'react-lgpd-consent/integrations'
import { ConsentProvider as ConsentProviderHeadless } from 'react-lgpd-consent/core'
Se encontrar dificuldades, consulte:
Última atualização: 01/12/2025