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
- 1Use the beforeInteractive strategyImport Script from next/script and load the PrivacyPill script with strategy="beforeInteractive". The default afterInteractive is too late.
- 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.
- 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.
- 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.
- 5VerifyCheck the initial HTML and network order on a direct page load and after client-side navigation, then test reject, accept and withdrawal.
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.tsxReplace 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- Shopify cookie bannertheme.liquid install + Customer Privacy API.
- Webflow cookie bannerPaste one line into Head code and publish.
- WordPress cookie bannerWPCode or functions.php, classic and block themes.
- Wix cookie bannerSettings → Custom Code → Head, all pages.
- Squarespace cookie bannerCode Injection → Header on Core plans and up.
- Framer cookie bannerProject Settings → Custom Code, start of head.
- Lovable cookie bannerOne prompt to the Lovable agent, or one tag.
- Bolt cookie bannerOne prompt to the Bolt agent, then publish.
- v0 cookie bannernext/script in the root layout, publish to Vercel.
- Ghost cookie bannerSettings → Code Injection → Site header.
- HubSpot cookie bannerSite header HTML + setHubSpotConsent sync.
Try it on your own site
Design your banner, preview it live, and paste one line. Free for one domain.