Platforms

How to add a cookie consent banner to a Next.js app

In a Next.js App Router project, render the snippet with next/script in app/layout.tsx with strategy="beforeInteractive", which injects it into the head before any Next.js code; in the Pages Router the same component goes in pages/_document.tsx. Mark Google and Meta tags with type="text/plain" and data-cq-category in the server-rendered HTML, or keep the Google tag loading under Consent Mode with denied defaults.
Published 20 October 2026 · By the Cookiequiet team · Markdown

Steps

Add the Script component to the root layoutIn app/layout.tsx, import Script from next/script and render it with src set to https://cookiequiet.com/cq.js, strategy="beforeInteractive" and the data-site, data-consent-mode and data-auto-block attributes. Next.js forwards data attributes to the final script tag and injects it into the head.
Pages Router: use _documentIf the project uses the Pages Router, render the same Script component in pages/_document.tsx. The beforeInteractive strategy is only allowed in the root layout or in _document.
Render marked tags server-sidePut the Meta Pixel or a hand-written Google tag in the root layout as plain script elements with type="text/plain" and data-cq-category, so they are in the initial HTML. Cookiequiet activates them when the category is granted or when a stored choice is applied on load.
Or run the Google tag in Consent ModeTo keep Google Analytics or Google Ads loading with denied defaults, render an inline beforeInteractive Script with an id containing Google's default consent block, then the Google tag with the default afterInteractive strategy, for example through @next/third-parties.
Gate client components on consentFor a YouTube player or a chat widget rendered by React, read window.Cookiequiet.get() and subscribe with window.Cookiequiet.on() in a client component, and render the embed only when the category is granted.
Add the Cookie preferences linkIn the footer component, render a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
Test with Reject allRun the app, open it in a private window, click Reject all and check that no _ga or _fbp cookie is set. Accept all, reload and confirm the tags load. Both choices appear in the consent log.

Where to load the snippet in Next.js

Next.js documents the beforeInteractive strategy of next/script for cookie consent managers: the script is injected into the initial HTML inside the head, fetched before any Next.js module and executed in the order it was placed, without blocking hydration. It must live in a root layout (app/layout.tsx, or app/[locale]/layout.tsx with internationalized routing). Next.js forwards data-* attributes to the final script tag.

tsx
// app/layout.tsx
import Script from "next/script";
​
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://cookiequiet.com/cq.js"
strategy="beforeInteractive"
data-site="YOUR_SITE_KEY"
data-consent-mode="1"
data-auto-block="1"
/>
</body>
</html>
);
}

Scripts with this strategy are injected into the head wherever you place the component, and they run once per document load: client-side navigations do not run them again, which is what you want. In the Pages Router, render the same component in pages/_document.tsx. You can read the key from process.env.NEXT_PUBLIC_COOKIEQUIET_SITE instead of writing it in the file.

How to handle the tags your app loads

A React app differs from a CMS in one way that matters: tags added after the page loaded are not in the initial HTML. Cookiequiet activates marked scripts when a stored choice is applied on load and whenever the visitor makes a choice, so marked tags belong in the server-rendered HTML.

Marked tags in the root layout. Render them as plain script elements in the layout, not through next/script:

tsx
<script
type="text/plain"
data-cq-category="marketing"
dangerouslySetInnerHTML={{ __html: "/* Meta Pixel base code, unchanged, from Events Manager */" }}
/>

Cookiequiet turns them into real scripts once the category is granted. See the Meta Pixel guide for the full base code and the noscript caveat.

Google tags in Consent Mode. If you prefer Google Analytics 4 or Google Ads to load with denied defaults and send cookieless pings until consent, add Google's default consent block as an inline beforeInteractive script (an id is required for inline scripts), then load the Google tag normally, for example with the GoogleAnalytics or GoogleTagManager components of @next/third-parties, which use the default afterInteractive strategy:

tsx
<Script id="cq-consent-defaults" 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>

Cookiequiet pushes the same defaults as soon as it runs and the consent update after every choice; the inline block only guarantees the order when the Google tag is loaded by a different mechanism. The Google Tag Manager guide covers the container and the cq_consent event.

Components that depend on consent. For a YouTube player, a map or a chat widget rendered by React, read the choice from the public API in a client component:

tsx
"use client";
import { useEffect, useState } from "react";
​
export function useConsent(category: string) {
const [granted, setGranted] = useState(false);
useEffect(() => {
const api = (window as any).Cookiequiet;
if (api === undefined) return;
const current = api.get();
setGranted(current ? current.c.includes(category) : false);
return api.on((s: { c: string[] }) => setGranted(s.c.includes(category)));
}, [category]);
return granted;
}

get() returns the stored choice with its categories in c, and on() returns an unsubscribe function. Render the iframe only when useConsent("marketing") is true; otherwise show a placeholder with a button that calls window.Cookiequiet.showPreferences(). For plain iframes in server components, data-cq-src instead of src is enough.

Render a plain anchor in the footer component; a hash link needs no next/link:

tsx
<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>

How to test

  1. Start the app (next dev works; the banner fetches its configuration from cookiequiet.com) and open it in a private window.
  2. Click Reject all. In the developer tools, the cookies should be cq_consent and your own session cookies: no _ga, no _fbp.
  3. Clear the cookies, click Accept all, reload: the marked scripts run and the Google tag receives analytics_storage: granted.
  4. Navigate between routes: the banner does not reappear, and the choice is still applied.
  5. Check the consent log in the dashboard.

What is specific to Next.js

  • One root layout, one snippet. Do not render the Script component in nested layouts as well; Next.js only allows beforeInteractive in a root layout, and a second copy would be ignored or duplicated.
  • Content Security Policy. With a nonce-based policy, pass the nonce to the Script component; allow https://cookiequiet.com in script-src and connect-src (configuration, geo lookup and consent records are fetched from that origin).
  • Static export and edge runtimes. The banner is a client script with no server dependency; output: "export" works.
  • Internationalized routing. The banner's language follows the visitor's browser, not the route locale; every string is editable per language in the dashboard.

For a plain static site built with Next.js, the article on cookie banners for static sites covers hosting on Vercel or Netlify.

Questions

afterInteractive scripts are injected client-side after hydration, so tags rendered in the HTML could run first. beforeInteractive scripts are in the initial HTML, inside the head, and run before any Next.js code, which is the order a consent script needs. Next.js names cookie consent managers as the typical use.
Next.js only supports the beforeInteractive strategy in a root layout (or in pages/_document.tsx with the Pages Router), because the script is injected into the initial HTML of every page. A nested layout cannot do that.
Yes. Render the same Script component with strategy="beforeInteractive" in pages/_document.tsx; data attributes are forwarded the same way.
Read window.Cookiequiet.get() and subscribe with window.Cookiequiet.on() in a client component, as in the useConsent hook above, and render the iframe only when the marketing category is granted. For iframes in server components, write data-cq-src instead of src and Cookiequiet loads them after consent.

Put a quiet banner on your website today

Free for one website with every core feature, no card and no trial clock. Upgrade a website to Pro for $3 a month when you want the AI designer, no branding and the five-year consent log.