Intro
What is GDPR Compliance?
- Get explicit consent before collecting user data
- Allow users to opt out of non-essential tracking
- Provide clear information about data collection
- Enable users to change their preferences at any time
What is Google Tag Manager (GTM)?
- Deploy various tracking scripts (tags)
- Manage multiple analytics tools
- Control when and how tags fire based on user consent
- Update tracking implementations without changing code
What is Google Analytics 4 (GA4)?
- Offers enhanced privacy features
- Supports consent mode
- Provides flexible data collection options
- Works seamlessly with GTM
Implementation Guide
Setup Dependencies
yarn add vanilla-cookieconsent
Initialize GTM with Default Denied State
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
{children}
);
}
Implement a Cookie Consent Banner
- Shows a privacy notice
- Allows granular consent choices (Accept all, Only Necessary, Manual setting)
- Updates GTM consent state based on user choices
- Implement the consent update logic
- Make sure to set your Domain or use a Environment Variable (Line 62)
// components/CookieConsent.tsx
'use client';
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import 'vanilla-cookieconsent/dist/cookieconsent.css';
import * as CookieConsent from 'vanilla-cookieconsent';
interface CookieConsentContextType {
acceptedServices: string[];
}
const CookieConsentContext = createContext(undefined);
export const useCookieConsent = (): CookieConsentContextType => {
const context = useContext(CookieConsentContext);
if (!context) {
throw new Error('useCookieConsent must be used within a CookieConsentProvider');
}
return context;
};
interface CookieConsentProviderProps {
children: ReactNode;
}
export const CookieConsentProvider: React.FC = ({ children }) => {
const [acceptedServices, setAcceptedServices] = useState([]);
useEffect(() => {
const updateGtmConsent = () => {
if (typeof window.gtag !== 'function') return;
const userPreferences = CookieConsent.getUserPreferences();
const acceptedCategories: string[] = userPreferences.acceptedCategories || [];
const acceptedServices = userPreferences.acceptedServices || {};
// Check if analytics category is accepted AND google is specifically enabled
const isGoogleAccepted = acceptedCategories.includes('analytics') &&
acceptedServices.analytics?.includes('google');
const consentUpdate = {
analytics_storage: isGoogleAccepted ? 'granted' : 'denied',
ad_storage: isGoogleAccepted ? 'granted' : 'denied',
personalization_storage: isGoogleAccepted ? 'granted' : 'denied',
functionality_storage: 'granted',
ad_user_data: isGoogleAccepted ? 'granted' : 'denied',
ad_personalization: isGoogleAccepted ? 'granted' : 'denied',
};
window.gtag('consent', 'update', consentUpdate);
};
const updateAcceptedServices = () => {
const userPreferences = CookieConsent.getUserPreferences();
const acceptedServices = userPreferences.acceptedServices || [];
const acceptedServicesList = Object.values(acceptedServices).flat();
setAcceptedServices(acceptedServicesList);
};
CookieConsent.run({
cookie: {
domain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN || 'your-domain.com',
},
guiOptions: {
consentModal: { layout: 'cloud' },
},
categories: {
necessary: {
enabled: true,
readOnly: true,
},
analytics: {
services: {
google: { label: 'Google Analytics' },
},
},
},
language: {
default: 'en',
translations: {
en: {
consentModal: {
title: 'Privacy Settings',
description: 'We use cookies to enhance your experience. Please choose your preferences.',
acceptAllBtn: 'Accept all',
acceptNecessaryBtn: 'Accept only necessary cookies',
showPreferencesBtn: 'Manage preferences',
},
preferencesModal: {
title: 'Cookie Preferences',
sections: [
{
title: 'Necessary',
description: 'Required for the site to function.',
linkedCategory: 'necessary',
},
{
title: 'Analytics',
description: 'Helps us understand site usage.',
linkedCategory: 'analytics',
},
],
acceptAllBtn: 'Accept all',
acceptNecessaryBtn: 'Accept only necessary cookies',
savePreferencesBtn: 'Save preferences',
},
},
},
},
onChange: () => {
updateAcceptedServices();
updateGtmConsent();
},
onFirstConsent: () => {
updateAcceptedServices();
updateGtmConsent();
},
});
const existingPreferences = CookieConsent.getUserPreferences();
if (existingPreferences && existingPreferences.acceptedCategories) {
updateAcceptedServices();
updateGtmConsent();
}
}, []);
return {children} ;
};
Use CookieConsentProvider
// app/providers.tsx
'use client';
import { CookieConsentProvider } from '@/components/CookieConsent';
interface ProvidersProps {
children: React.ReactNode;
}
export default function Providers({ children }: ProvidersProps) {
return (
{children}
);
}
Use Provider in layout
Use the Provider code in our Layout file
// app/layout.tsx
import Providers from '@/app/providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
Adding New Tracking Services to Cookie Consent
- Add a new Service to e.g. the Analytics Categories inside CookieConsent.run()
- Add new function e.g. updateXConsent()
- Add usage in onChange and onFirstConsent
- Update the Modal Text if you added a new category
Adding a Privacy Settings Page to Your Next.js App
// app/settings/privacy/page.tsx
'use client';
import { Button, Stack, Text } from '@mantine/core'; // or any UI library
import * as CookieConsent from 'vanilla-cookieconsent';
export default function PrivacyPage() {
return (
Privacy Settings
{/* Cookie Settings */}
Cookie Settings
Control how we use cookies on our website. Click the button below
to manage your cookie preferences.
);
}
Conclusion
- User privacy rights
- Technical implementation
- Consent management
- Data handling practices
Links
Best, Julian