Tilbake til bloggen
Technical

Next.js Cookie Consent: The Complete Guide

GetCookies-teametJanuary 5, 20258 min lesing
Next.jsReactImplementationTutorial

TLDR: Cookie consent in Next.js should take minutes, not days. One Script tag in your layout, full GDPR compliance, zero GTM configuration. Works with App Router and Pages Router.

Read full summary

Next.js developers waste days configuring cookie consent. Google Consent Mode v2 docs, GTM triggers, timing issues. This guide shows the 2-minute path: add GetCookies, ship your feature, move on.

Summary by Claude AI

The Day We Almost Missed a Launch

We were shipping a client's Next.js app. Three days from launch.

"Hey, we need GDPR compliance for EU users."

We'd done this before. Or so we thought. We spent the next two days:

  • Reading Google Consent Mode v2 documentation
  • Configuring 23 GTM triggers
  • Debugging why consent defaults weren't firing before gtag
  • Watching my Lighthouse score drop from 98 to 74
  • Still not being sure if it actually worked

We delayed the launch.

There's a better way. We wish we'd known it then.

The 2-Minute Setup

App Router (Next.js 13+)

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

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <Script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id="YOUR_DOMAIN_ID"
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  )
}

Pages Router (Next.js 12 and earlier)

// pages/_app.tsx
import Script from 'next/script'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        src="https://getcookies.co/api/v1/widget/loader.js"
        data-domain-id="YOUR_DOMAIN_ID"
        strategy="afterInteractive"
      />
      <Component {...pageProps} />
    </>
  )
}

That's it. Refresh your site. You'll see a cookie banner.

You now have:

  • GDPR-compliant consent collection
  • Google Consent Mode v2 (all four signals)
  • Automatic script blocking
  • Third-party pixel consent (Meta, TikTok, Google)
  • Consent logging for audits

No GTM. No configuration. No documentation rabbit holes.

Why afterInteractive?

Next.js Script component has three loading strategies:

Strategy When It Runs Best For
beforeInteractive Before page hydration Critical, render-blocking scripts
afterInteractive After hydration starts GetCookies (recommended)
lazyOnload During idle time Non-essential scripts

afterInteractive is the sweet spot because:

  1. Your page renders fast (good Lighthouse scores)
  2. GetCookies sets consent defaults before users interact
  3. The banner appears quickly after the page loads

Environment Variables: Dev vs Production

You probably want different domain IDs for development and production:

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

const DOMAIN_ID = process.env.NEXT_PUBLIC_GETCOOKIES_DOMAIN_ID

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        {DOMAIN_ID && (
          <Script
            src="https://getcookies.co/api/v1/widget/loader.js"
            data-domain-id={DOMAIN_ID}
            strategy="afterInteractive"
          />
        )}
      </head>
      <body>{children}</body>
    </html>
  )
}

Set up your environments:

# .env.local (development)
NEXT_PUBLIC_GETCOOKIES_DOMAIN_ID=dev-domain-id

# .env.production
NEXT_PUBLIC_GETCOOKIES_DOMAIN_ID=prod-domain-id

Create separate domains in the GetCookies dashboard for localhost, staging, and production. Each gets independent consent tracking.

Loading Analytics After Consent

Method 1: The JavaScript API

Want fine-grained control? Use the GetCookies events:

// components/Analytics.tsx
'use client'

import { useEffect } from 'react'

export function Analytics() {
  useEffect(() => {
    const handleConsent = (e: CustomEvent) => {
      const { analytics, marketing } = e.detail

      if (analytics) {
        window.gtag?.('config', 'G-XXXXXXXX')
      }

      if (marketing) {
        window.fbq?.('init', 'XXXXXXXXXX')
      }
    }

    window.addEventListener('getcookies:consent', handleConsent as EventListener)

    // Check if consent already exists
    if (window.GetCookies?.hasConsent('analytics')) {
      window.gtag?.('config', 'G-XXXXXXXX')
    }

    return () => {
      window.removeEventListener('getcookies:consent', handleConsent as EventListener)
    }
  }, [])

  return null
}

Method 2: Data Attributes (Simpler)

GetCookies can auto-manage scripts using data attributes:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <Script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id="YOUR_DOMAIN_ID"
          strategy="afterInteractive"
        />

        {/* This script only loads after analytics consent */}
        <script
          data-cookieconsent="analytics"
          type="text/plain"
          src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"
        />
      </head>
      <body>{children}</body>
    </html>
  )
}

The type="text/plain" prevents the script from running. GetCookies changes it to text/javascript after consent.

Server Components: No Special Handling Needed

React Server Components work seamlessly with GetCookies:

// app/page.tsx (Server Component)
export default function Page() {
  return (
    <main>
      <h1>Welcome</h1>
      {/* GetCookies banner renders client-side */}
      {/* No hydration mismatch issues */}
    </main>
  )
}

GetCookies injects its UI into the DOM client-side. It doesn't require server rendering. Server Components render normally.

TypeScript Types

Add type definitions for autocomplete:

// types/getcookies.d.ts
declare global {
  interface Window {
    GetCookies?: {
      hasConsent: (category: 'analytics' | 'marketing' | 'preferences') => boolean
      showPreferences: () => void
      acceptAll: () => void
      rejectAll: () => void
    }
  }
}

export {}

Now you can use the API with full type safety:

'use client'

export function CookieSettingsButton() {
  return (
    <button onClick={() => window.GetCookies?.showPreferences()}>
      Cookie Settings
    </button>
  )
}

Common Patterns

Footer Cookie Settings Link

// components/Footer.tsx
'use client'

export function Footer() {
  return (
    <footer className="py-8 border-t">
      <button
        onClick={() => window.GetCookies?.showPreferences()}
        className="text-sm text-gray-500 hover:underline"
      >
        Cookie Settings
      </button>
    </footer>
  )
}

Consent-Gated Personalization

'use client'

import { useState, useEffect } from 'react'

export function PersonalizedContent() {
  const [hasConsent, setHasConsent] = useState(false)

  useEffect(() => {
    setHasConsent(window.GetCookies?.hasConsent('marketing') ?? false)

    const handler = () => {
      setHasConsent(window.GetCookies?.hasConsent('marketing') ?? false)
    }

    window.addEventListener('getcookies:consent', handler)
    return () => window.removeEventListener('getcookies:consent', handler)
  }, [])

  if (!hasConsent) {
    return <GenericRecommendations />
  }

  return <PersonalizedRecommendations />
}

Deploying Everywhere

GetCookies works on every Next.js deployment target:

Vercel: Works out of the box.

vercel --prod

Netlify: Works out of the box.

# netlify.toml
[build]
  command = "npm run build"
  publish = ".next"

Cloudflare Pages: Works with @cloudflare/next-on-pages.

Docker: Works in any container.

Troubleshooting

Banner not showing?

  1. Check your domain ID in the GetCookies dashboard
  2. Verify the domain is added and matches your URL
  3. Check browser console for errors
  4. Check Network tab to see if the script loads

Consent not persisting?

  1. Check that localStorage isn't being cleared
  2. Verify domain matches exactly (www vs non-www)
  3. Look for conflicting consent scripts

TypeScript errors? Add the types file above, or use the quick fix:

(window as any).GetCookies?.showPreferences()

Performance: The Numbers

GetCookies is built for speed:

Metric GetCookies Typical CMP
Initial load ~1.6 KB 40-150KB
LCP impact None Often 100ms+
CLS impact None Often significant
Lighthouse hit 0-2 points 10-20 points

Your Lighthouse scores stay where they belong.

What You Don't Have to Do

  • Configure GTM triggers
  • Read Consent Mode v2 documentation
  • Debug timing issues
  • Set up consent update events
  • Test with Tag Assistant for hours
  • Wonder if it actually works

Just add the script and ship your feature.


Ready to add cookie consent to your Next.js app?

Get your domain ID in 60 seconds

G

GetCookies-teamet

Skrevet og vedlikeholdt av GetCookies-teamet i Getia AS i Oslo. Artikkelen er generell informasjon om samtykke til informasjonskapsler, ikke juridisk rådgivning.

Skann ditt eget nettsted gratis

Gratisplanen gir deg banneret på ett nettsted og tre cookie-skanninger i måneden. Uten kort, uten salgssamtale.