Cookie consent for Next.js

The banner loads with next/script and strategy="beforeInteractive" — the strategy Next.js documents for consent managers — so it runs before your analytics. Tags you load through next/script or @next/third-parties are held back until the visitor agrees, and Google tags get Consent Mode v2 signals.

Set it up with your AI agent

Connect the Croombit MCP server to your editor or coding agent and paste this. It creates the site, the cookie categories and the banner, and edits your code.

Using Croombit: create a site for this project with Analytics (ANALYTICS) and Advertising (MARKETING) cookie categories, off by default, publish them and publish the banner. Create a TEST publishable key for localhost:3000. This is a Next.js App Router project: load cmp.js in the root layout with next/script and strategy="beforeInteractive", reading the key from NEXT_PUBLIC_CROOMBIT_KEY in .env.local, and add a "Cookie settings" link to the footer.

The agent works in TEST. Going live takes two steps in the console: verify your domain and swap in a LIVE key — see going live.

Or by hand

1. Load the banner in the root layout

Put the key in .env.local as NEXT_PUBLIC_CROOMBIT_KEY (pk_test_… in development, pk_live_… in production). beforeInteractive only works in the root layout.

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* beforeInteractive: loaded before any other script, as consent managers need */}
        <Script
          src="https://croombit.com/cmp.js"
          strategy="beforeInteractive"
          data-tc-api-key={process.env.NEXT_PUBLIC_CROOMBIT_KEY}
        />
        {children}
      </body>
    </html>
  );
}

2. Your analytics need no changes

Scripts loaded through next/script are inserted by JavaScript, which cmp.js intercepts: known hosts stay parked until the visitor grants their category.

// Any tag loaded through next/script or @next/third-parties is inserted
// by JavaScript, so cmp.js holds known hosts (gtag.js, GTM, Meta Pixel…)
// until the visitor grants the matching category.
import { GoogleAnalytics } from '@next/third-parties/google';

<GoogleAnalytics gaId="G-XXXX" />

For a tag the blocklist doesn't know, render it with type="text/plain" and data-tc-consent="analytics" (or marketing) instead.

'use client';
// Any element with data-tc-open-preferences reopens the panel,
// pre-set to the visitor's current choices.
export function CookieSettingsLink() {
  return <a href="#" data-tc-open-preferences>Cookie settings</a>;
}

4. Record the consents your app asks for

Newsletter opt-ins, terms acceptance and the like are recorded from your server — a Route Handler or Server Action — with a secret key. Each one lands in the append-only vault with a signed receipt. See Record and query consent.

import { CroombitClient } from '@croombit/sdk';

// Server side, with a secret key — e.g. in your signup handler.
const tc = new CroombitClient({
  baseUrl: 'https://api.croombit.com',
  apiKey: process.env.CROOMBIT_SECRET_KEY!,
});
await tc.recordConsent(
  { subject: 'email:' + user.email, purpose: 'newsletter', state: 'GRANTED', channel: 'signup_form' },
  { idempotencyKey: crypto.randomUUID() },
);

Questions

Does it work with the Pages Router?

Yes. Put the same <Script strategy="beforeInteractive"> in pages/_document.tsx, inside <Head>.

Does client-side navigation show the banner again?

No. The decision is stored once and applies to every route; the banner only reappears if you publish a version that requires re-consent.

Can I keep my own banner design?

Yes — the banner designer covers layout, colours and copy. For a fully custom UI, use the JavaScript SDK instead of cmp.js; see the cookie banner guide.