Next.js cookie banner

Add a GDPR cookie banner to a Next.js app

In Next.js the consent script has to load before hydration and before your analytics. next/script's beforeInteractive strategy does exactly that — as long as it lives in the root layout (App Router) or _document (Pages Router).

Updated 28 October 2026

Set it up in 5 steps

  1. 1Use the beforeInteractive strategyImport Script from next/script and load the PrivacyPill script with strategy="beforeInteractive". The default afterInteractive is too late.
  2. 2App Router: root layout onlyAdd both scripts to app/layout.tsx (or src/app/layout.tsx). If you use several root layouts via route groups, add them to each. Next.js injects beforeInteractive scripts into the <head> wherever they appear in the root layout.
  3. 3Pages Router: _documentAdd them to pages/_document.tsx inside <Head>, keeping Html, Head, Main and NextScript. beforeInteractive doesn't work in _app.tsx or individual pages.
  4. 4Keep it ahead of trackersIf you use @next/third-parties or other beforeInteractive tags, place PrivacyPill first. Google tags will read the Consent Mode defaults it sets.
  5. 5VerifyCheck the initial HTML and network order on a direct page load and after client-side navigation, then test reject, accept and withdrawal.
TSX · app/layout.tsx (App Router)
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* Consent Mode v2 defaults — denied until the visitor chooses */}
        <Script id="pp-consent-default" strategy="beforeInteractive">
          {`window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('consent','default',{ad_storage:'denied',ad_user_data:'denied',ad_personalization:'denied',analytics_storage:'denied',functionality_storage:'denied',personalization_storage:'denied',security_storage:'granted',wait_for_update:500});`}
        </Script>
        <Script src="https://privacy-pill-consent-studio.lovable.app/api/public/v1/pp.js?site=pp_your_site_key" strategy="beforeInteractive" />
        {children}
      </body>
    </html>
  );
}

// Pages Router: put the same two <Script> tags inside <Head> in pages/_document.tsx

Replace pp_your_site_key with your site key — you'll get it when you save your banner.

Gating non-Google scripts

For scripts that don't understand Consent Mode, render them as <script type="text/plain" data-pp-category="analytics"> in your layout, or start them from a PrivacyPill.on() listener when e.consent.analytics is true.

Server components and consent

Consent lives in the browser. If a server component needs to know the choice, read it client-side with PrivacyPill.get() and pass it through your own state — don't block rendering on it.

Frequently asked questions

Set up PrivacyPill on other platforms

See all

Try it on your own site

Design your banner, preview it live, and paste one line. Free for one domain.