[Consent banner](/consent-banner)

# Add a cookie consent banner to Next.js

c15t puts the banner in your Next.js app as code. It works with the App Router, holds tags until the visitor chooses and keeps Google Consent Mode v2 in sync.

[Start for free](/dashboard/sign-up) · [See the code](#install)

Ask your coding agent: `Add inth to my app: inth.com/SKILL.md`

- **Framework**: Next.js 15 and 16
- **Library**: c15t, open source
- **Code checked**: 28 October 2026

## Install c15t in a Next.js app

c15t is the open source consent library behind Inth Consent. It renders the banner from your own bundle and records each choice in your Inth project.

```bash
npm install c15t @c15t/integrations
```

Add your Inth project URL to `.env.local`. Copy it from your Inth dashboard.

```env
NEXT_PUBLIC_C15T_BACKEND_URL=https://your-project.inth.app
```

Wrap your Next.js config. The build fetches your consent policy once and caches it, so rebuild after you change a policy.

```ts
import { withConsentManifest } from 'c15t/next/build';
import type { NextConfig } from 'next';

const nextConfig = {} satisfies NextConfig;

export default withConsentManifest(nextConfig);
```

Prefer one command? `npx @inth/cli c15t` connects your Inth project and starts your agent with the c15t setup steps for you to review. Code on this page comes from the [c15t Next.js quickstart](https://c15t.com/docs/frameworks/next/quickstart), checked 28 October 2026.

## App Router and Pages Router setup

In the App Router, the root layout stays a Server Component, so you write no `'use client'` wrapper. Pass `resolveConsent()` to `ConsentRoot` without awaiting it. It reads the visitor’s cookies and location headers on the server, so the browser makes no `/init` request.

```tsx
import { ConsentBanner, ConsentDialog, ConsentDialogLink, ConsentRoot } from 'c15t/next';
import { resolveConsent } from 'c15t/next/server';
import type { ReactNode } from 'react';
import './globals.css';

const RootLayout = ({ children }: { children: ReactNode }) => (
  <html lang="en">
    <body>
      {/* Not awaited: the page renders while consent resolves. */}
      <ConsentRoot state={resolveConsent()}>
        {children}
        <ConsentBanner />
        <ConsentDialog />
        <footer>
          <ConsentDialogLink>Privacy settings</ConsentDialogLink>
        </footer>
      </ConsentRoot>
    </body>
  </html>
);

export default RootLayout;
```

The Pages Router, static export with `output: 'export'` and client only setups each have their own guide in the [c15t Next.js docs](https://c15t.com/docs/frameworks/next/quickstart#use-another-router-or-deployment).

## Hold scripts until consent

List each tag in `c15t.config.ts`. With `loadMode: 'after-consent'`, the script stays off the page until the visitor accepts its category.

```ts
import { posthog } from '@c15t/integrations/posthog';
import { defineConsentConfig } from 'c15t/next';

export default defineConsentConfig({
  scripts: [
    posthog({
      id: 'phc_your_project_key',
      initOptions: { cookieless_mode: 'never' },
      loadMode: 'after-consent',
    }),
  ],
});
```

Remove the tag’s own script or SDK setup so it loads only through c15t. Step by step guides for common tags:

- [Google Tag Manager in Next.js](/blog/how-to-add-google-tag-manager-nextjs-c15t)
- [Meta Pixel in Next.js](/blog/how-to-integrate-meta-pixel-nextjs-c15t)
- [LinkedIn Insight Tag in Next.js](/blog/how-to-add-linkedin-insight-tag-nextjs-c15t)
- [PostHog in Next.js](/blog/how-to-add-posthog-to-next-js-with-gdpr-aware-consent)

## Vercel Analytics and Speed Insights

If you deploy on Vercel, hold its analytics until the visitor accepts measurement. Remove the `Analytics` and `SpeedInsights` components first, then add the c15t helpers.

```ts
import { vercelAnalytics } from '@c15t/integrations/vercel-analytics';
import { vercelSpeedInsights } from '@c15t/integrations/vercel-speed-insights';

// Add these to the scripts array in c15t.config.ts
scripts: [vercelAnalytics(), vercelSpeedInsights()],
```

Until measurement has consent, Vercel Analytics stays off the page and Speed Insights drops every Web Vitals report. Neither helper sends a consent signal to Vercel. See the [Vercel Analytics](https://c15t.com/docs/integrations/vercel-analytics) and [Speed Insights](https://c15t.com/docs/integrations/vercel-speed-insights) guides.

## Google Consent Mode v2 in Next.js

The Google tag helper sends the Consent Mode v2 default before your config and an update after each choice.

```ts
import { gtag } from '@c15t/integrations/google-tag';

export const scripts = [gtag({ id: 'G-XXXXXXXXXX', category: 'measurement' })];
```

Measurement maps to `analytics_storage`. Marketing maps to `ad_storage`, `ad_user_data` and `ad_personalization`. By default the helper loads gtag.js before a choice and Google receives a default that matches the current permissions. Set `loadMode: 'after-consent'` to send nothing to Google until the visitor accepts. [Read the developer guide to Consent Mode v2](/blog/developer-guide-to-consent-mode-v2).

## Show the right banner in each region

Your Inth project sets a policy for each region, such as opt in for the EU and UK or opt out for California. On the server, `resolveConsent()` reads the visitor’s location from your host’s headers, such as `x-vercel-ip-country` on Vercel, then applies the matching policy.

If your host strips those headers before Server Components run, add `c15tProxy` in `proxy.ts` on Next.js 16 or `c15tMiddleware` in `middleware.ts` on Next.js 15. Without location headers, c15t applies your unknown location rule. See [geography headers](https://c15t.com/docs/frameworks/next/geography-headers).

Test each region before launch. [See how regional policies work](/consent-banner/regions).

## Keep the banner off your critical path

The banner ships in your bundle, so there is no vendor script to fetch first. Next.js renders the page without waiting for consent. The banner renders on the server and arrives later in the same response, before the page hydrates. Pass `streamBanner: false` to mount it after hydration instead.

In CookieBench’s Next.js desktop run, the c15t banner appeared in a median 27 ms. CookieBench cookiebench-v3.2.4, published 12 October 2026. Inth, the team behind c15t, maintains CookieBench, and c15t is one of the tools measured. Lab results; your site may differ. [Run limits](https://cookiebench.com/benchmarks/c15t#known-issues).

Measure your own pages too. A lab result is not a field result.

## Test consent gates with Playwright

Check these four things before you ship:

1. Under an opt in policy, no PostHog request leaves the page before a choice.
2. After Reject All and a reload, PostHog stays off.
3. Turning on measurement in Privacy settings loads it.
4. Turning measurement off reloads the page without it.

Choices post to your Inth project, so you can check that each one was recorded. [Write these as Playwright tests](/blog/test-consent-gates-nextjs-playwright).

## Use Inth in production

c15t is open source. Point it at your Inth project, which hosts the backend and keeps the consent records. [See Inth open source](/oss).

c15t also has a local mode with no backend. It keeps no consent records, so the docs advise against it in production.

Want to see what else your repo sends before consent? [Run a free codebase scan](/run-free-codebase-scan).

## Questions about Next.js consent

### Does it work with Server Components and the App Router?

Yes. The root layout stays a Server Component, and `resolveConsent()` reads consent on the server.

### How do I load GTM, Meta Pixel, LinkedIn and PostHog only after consent?

Add each tag’s c15t helper to `c15t.config.ts` and remove its own script. Guides: [GTM](/blog/how-to-add-google-tag-manager-nextjs-c15t), [Meta Pixel](/blog/how-to-integrate-meta-pixel-nextjs-c15t), [LinkedIn](/blog/how-to-add-linkedin-insight-tag-nextjs-c15t) and [PostHog](/blog/how-to-add-posthog-to-next-js-with-gdpr-aware-consent).

### How do I set Consent Mode v2 defaults before gtag loads?

Use the c15t Google tag helper. It sends the default before your config and an update after each choice.

### How do I show different banners by region on Vercel or another host?

Set a policy for each region in your Inth project. `resolveConsent()` reads your host’s location headers, such as `x-vercel-ip-country` on Vercel, and applies the matching policy. [Regional policies](/consent-banner/regions).

### Does the banner cause layout shift or hurt LCP?

The banner ships in your bundle, so there is no vendor script to fetch first. Measure your own pages in Lighthouse and field data before and after you add it.

### Can I run it without a backend?

c15t has a local mode, but it keeps no consent records. Use an Inth project in production.

### How do I test that tags stay blocked after refusal?

Click Reject All, reload and check that no request goes to the tag. [Playwright recipe](/blog/test-consent-gates-nextjs-playwright).

### Does it hold Vercel Analytics and Speed Insights?

Yes. The c15t helpers keep both off until the visitor accepts measurement.