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.Steps
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.tsximport Script from "next/script";export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="en"><body>{children}<Scriptsrc="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<scripttype="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.
How to add the Cookie preferences link
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
- Start the app (
next devworks; the banner fetches its configuration from cookiequiet.com) and open it in a private window. - Click Reject all. In the developer tools, the cookies should be
cq_consentand your own session cookies: no_ga, no_fbp. - Clear the cookies, click Accept all, reload: the marked scripts run and the Google tag receives
analytics_storage: granted. - Navigate between routes: the banner does not reappear, and the choice is still applied.
- 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
beforeInteractivein 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.cominscript-srcandconnect-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.