This guide demonstrates how we implemented Google Analytics with full GDPR compliance in a Next.js 15 application using the app router. Our solution features granular cookie consent management, Google Analytics Consent Mode v2, and a privacy-first approach that ensures compliance with European data protection regulations.
Our Next.js 15 application uses the app router with the following key characteristics:
The application structure includes a main layout with header, navigation, and footer components, all wrapped in a theme provider that supports system preferences and manual theme switching.
European GDPR regulations require websites to obtain explicit user consent before setting analytics cookies. Our solution addresses several key requirements:
Legal Compliance: We implement Google Analytics Consent Mode v2, which allows analytics to function in a privacy-preserving way even before user consent, then upgrades to full tracking once consent is granted.
User Control: Users can granularly control different cookie categories (necessary, analytics, preferences, marketing) and easily modify their preferences at any time.
Developer Experience: The system integrates seamlessly with Next.js 15's app router without causing build issues or requiring complex workarounds.
Performance: Analytics scripts only load in production, and consent preferences are stored locally for immediate application on subsequent visits.
Our implementation consists of four main components:
The system defaults to denying all non-essential cookies, displays a consent banner to new visitors, and applies user preferences immediately upon consent.
We store the Google Analytics ID in environment variables for security and flexibility across environments:
# .env.local
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
This approach allows us to use different analytics properties for development, staging, and production environments while keeping sensitive configuration out of version control.
Our root layout component (src/app/layout.tsx) integrates Google Analytics with consent mode:
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const GA_ID = process.env.NEXT_PUBLIC_GA_ID;
return (
<html lang="en" suppressHydrationWarning>
<head>
{/* Google Analytics - Only load in production and after consent */}
{process.env.NODE_ENV === "production" && GA_ID && (
<>
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`}
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
// Set default consent to denied - will be updated when user consents
gtag('consent', 'default', {
'analytics_storage': 'denied',
'ad_storage': 'denied',
'functionality_storage': 'denied',
'personalization_storage': 'denied',
'security_storage': 'granted'
});
// Initialize GA with consent mode
gtag('config', '${GA_ID}', {
'consent_mode': 'advanced'
});
`}
</Script>
</>
)}
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<ThemeBridge />
<div className="flex flex-col min-h-screen px-4 sm:px-36">
<LinksSheet />
<Header />
<main className="flex-1 flex flex-col min-h-0 overflow-y-auto overflow-x-hidden sm:pt-40 pt-32">
{children}
<CookieConsentManager />
</main>
<Footer />
</div>
</ThemeProvider>
</body>
</html>
);
}
Key Implementation Details:
NODE_ENV === "production", preventing development trackingsecurity_storage which remains grantedCookieConsentManager is placed within the main content area for proper styling and accessibilityOur consent manager (src/components/compliance/cookie-consent-manager.tsx) handles the complete consent lifecycle:
export interface CookieConsent {
necessary: boolean; // Always true, can't be disabled
analytics: boolean; // Google Analytics
preferences: boolean; // User preferences
marketing: boolean; // Future marketing cookies
timestamp: number;
version: string;
}
const DEFAULT_CONSENT: CookieConsent = {
necessary: true,
analytics: false,
preferences: false,
marketing: false,
timestamp: Date.now(),
version: "2.0",
};
The consent manager implements several key patterns:
State Management: Uses React hooks to manage consent state and banner visibility, with immediate localStorage persistence.
Consent Application: When consent changes, the system immediately applies new settings to Google Analytics using the consent update API.
Error Handling: Gracefully handles localStorage parsing errors and provides fallback behavior.
Version Control: Includes consent version tracking for future consent requirement changes.
Our analytics utility library (src/lib/analytics.ts) provides clean interfaces for consent management:
// Consent management for GDPR compliance
export const updateConsent = (consentSettings: {
analytics_storage?: "granted" | "denied";
ad_storage?: "granted" | "denied";
functionality_storage?: "granted" | "denied";
personalization_storage?: "granted" | "denied";
security_storage?: "granted" | "denied";
}) => {
if (typeof window !== "undefined" && window.gtag) {
window.gtag("consent", "update", consentSettings);
}
};
This abstraction layer provides type-safe consent management and ensures the gtag API is available before calling it.
Our consent banner provides a developer-themed interface matching our application's aesthetic:
Progressive Disclosure: The banner initially shows simple accept/reject options, with a "customize" button that reveals granular controls.
Accessibility: Full keyboard navigation, ARIA labels, and semantic HTML structure.
Visual Design: Matches our application's theme system with consistent typography and color schemes.
Responsive Design: Works seamlessly across mobile and desktop viewports.
The banner uses function-style button labels (acceptAll(), rejectAll(), customize()) that align with our application's developer-focused branding.
We test the implementation across several scenarios:
Initial Visit: New users see the consent banner with analytics disabled by default.
Consent Persistence: User preferences persist across browser sessions and page reloads.
Analytics Activation: Google Analytics only begins full tracking after explicit user consent.
Preference Changes: Users can modify their consent choices, with changes applied immediately.
Production Verification: Analytics scripts only load in production builds, preventing development environment tracking.
Our deployment process ensures proper analytics functionality:
NEXT_PUBLIC_GA_ID in production environmentPrivacy-First: Default denial of all tracking with explicit opt-in requirement meets GDPR standards.
Performance Optimized: Analytics scripts only load when needed, reducing bundle size in development.
User Experience: Clean, intuitive interface that doesn't overwhelm users with complex privacy options.
Maintainable Code: Clear separation of concerns with reusable components and type-safe interfaces.
Future-Proof: Structured to easily accommodate additional cookie categories or consent requirements.
This implementation provides a solid foundation for GDPR-compliant analytics in Next.js 15 applications while maintaining excellent developer and user experiences.
Tech Stack: Next.js 15, React, TypeScript, Tailwind CSS, Google Analytics 4 Compliance: GDPR, Google Consent Mode v2 Key Features: Granular consent, localStorage persistence, production-only loading