React / Next.js

React & Next.js Cookie Banner: GDPR Consent Implementation Guide (2026)

Building a GDPR-compliant cookie consent banner in React has two hard parts: blocking third-party scripts before consent fires, and persisting consent state without breaking SSR. Here's how to do both correctly.

Updated March 2026 · 10 min read · Free GDPR checklist →

Why Cookie Consent Is Harder in React Apps

In a traditional server-rendered website, adding a cookie banner is straightforward — you inject a script into the HTML head. In React and Next.js apps, several challenges arise:

Two Approaches: Build vs. Generate

Option A: Vanilla JS snippet

A generated, self-contained JavaScript file that manages consent without React dependencies. Loads synchronously, blocks scripts immediately, works in any React framework. Zero bundle impact.

Option B: React component

A React component using useState/useEffect for consent state. More idiomatic for React apps but requires careful SSR handling and careful script-load coordination.

For most production apps, the vanilla JS approach (Option A) is more reliable. It loads before React hydrates, prevents any flash of unblocked scripts, and doesn't add to your bundle. The generated cookie banner from our kit uses this approach.

The GDPR Requirements Your Banner Must Meet

Integrating the Generated Banner in Next.js (App Router)

The generated vanilla JS banner from our kit is added as an inline script in your root layout. For Next.js App Router:

app/layout.tsx
import Script from 'next/script'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        {/* Cookie banner loads synchronously before React hydrates */}
        <Script
          src="/cookie-banner.js"
          strategy="beforeInteractive"
        />
      </head>
      <body>
        {children}
      </body>
    </html>
  )
}

Why strategy="beforeInteractive"? This tells Next.js to load the script before React hydration begins — ensuring consent state is read from localStorage and tracking scripts are blocked before any analytics code can run. It's the only strategy that reliably prevents a consent window where scripts could briefly execute.

Conditionally Loading Analytics After Consent

With the generated banner in place, your analytics scripts should only load after consent is granted. Here's the pattern for Google Analytics in Next.js:

components/Analytics.tsx
'use client'

import Script from 'next/script'
import { useEffect, useState } from 'react'

export function Analytics() {
  const [analyticsConsent, setAnalyticsConsent] = useState(false)

  useEffect(() => {
    // Read consent from localStorage (set by the cookie banner)
    const consent = localStorage.getItem('cookie-consent')
    if (consent) {
      const parsed = JSON.parse(consent)
      setAnalyticsConsent(parsed.analytics === true)
    }

    // Listen for consent updates
    const handler = (e: Event) => {
      const detail = (e as CustomEvent).detail
      setAnalyticsConsent(detail.analytics === true)
    }
    window.addEventListener('cookieConsentUpdated', handler)
    return () => window.removeEventListener('cookieConsentUpdated', handler)
  }, [])

  if (!analyticsConsent) return null

  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_ID}`}
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${process.env.NEXT_PUBLIC_GA_ID}');
        `}
      </Script>
    </>
  )
}

Then add <Analytics /> to your root layout. The component renders nothing until analytics consent is granted, at which point it loads the Google Analytics script.

Pages Router Integration

For Next.js Pages Router (pages/_app.tsx):

pages/_app.tsx
import type { AppProps } from 'next/app'
import Head from 'next/head'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        {/* Inline the banner script directly for Pages Router */}
        <script src="/cookie-banner.js" />
      </Head>
      <Component {...pageProps} />
    </>
  )
}

Plain React (Vite / Create React App)

For React apps not using Next.js, add the script to your index.html:

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My App</title>
    <!-- Cookie banner loads before React bundle -->
    <script src="/cookie-banner.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Hydration Safety: Avoiding SSR Mismatches

If you build a React cookie consent component, you'll run into a common SSR issue: the server renders with no consent state, while the client reads from localStorage and renders the banner differently. This causes a hydration mismatch warning (or silent visual bug).

The solution is to suppress the banner rendering on the initial server render:

components/CookieBanner.tsx
'use client'

import { useState, useEffect } from 'react'

export function CookieBanner() {
  // Start as null (no render) to match server output
  const [consent, setConsent] = useState<string | null>(null)

  useEffect(() => {
    // Only runs on client — safe to read localStorage
    setConsent(localStorage.getItem('cookie-consent'))
  }, [])

  // Don't render anything until client hydration completes
  if (consent === null) return null
  // Don't show banner if user has already chosen
  if (consent !== '') return null

  return (
    <div className="cookie-banner">
      {/* Banner UI */}
    </div>
  )
}
⚠ Don't Skip the Privacy Policy

Your cookie banner must link to your privacy policy before users give consent. Clicking "Accept" without being able to review what they're consenting to does not constitute valid GDPR consent. Make sure your banner includes a "Privacy Policy" link.

Testing Your Cookie Consent Implementation

Before going live, verify your implementation with these checks:

  1. Open your site in a fresh private/incognito window
  2. Open DevTools Network tab and filter for google-analytics.com and facebook.com
  3. Verify zero requests to tracking domains before you click "Accept"
  4. Click "Accept" — verify analytics requests begin
  5. Reload the page — verify the banner does not re-appear
  6. Find your "Cookie preferences" link (footer or banner) and click "Reject all"
  7. Reload — verify analytics requests stop

You can also use the Cookie Consent Check tool or browser extensions like "EditThisCookie" to inspect what's being set.

Get the generated cookie banner + full compliance pack

The Compliance Starter Pack generates a production-ready vanilla JS cookie banner, privacy policy, and terms of service tailored to your jurisdiction. Drop it into any React or Next.js app in minutes.

Generate Developer Pack — $6.99
Vanilla JS · Zero dependencies · Works with React, Next.js, Vite, Remix

This guide is for informational purposes only and does not constitute legal advice. Framework APIs change frequently; verify against current Next.js and React documentation. For complex compliance requirements, consult a qualified data protection attorney.