[Consent banner](/consent-banner)

# Add a cookie consent banner to Nuxt

The c15t Nuxt module puts the banner in your Nuxt app as code. It 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**: Nuxt with server rendering
- **Library**: c15t Nuxt module, open source
- **Code checked**: 28 October 2026

## Install the c15t Nuxt module

c15t is the open source consent library behind Inth Consent. Its Nuxt module renders the banner on the server and records each choice in your Inth project.

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

Register `c15t/vue` in `nuxt.config.ts`. This is the Nuxt module, so there is no separate Nuxt package to install.

```ts
export default defineNuxtConfig({
  compatibilityDate: '2026-07-04',
  modules: ['c15t/vue'],
});
```

Add your Inth project URL to `.env`. The module reads it, so the config needs no `backendURL`. The build gets your policy one time, so build again after you change a policy.

```bash
NUXT_PUBLIC_C15T_BACKEND_URL=https://your-project.inth.app
```

Code on this page comes from the [c15t Nuxt quickstart](https://c15t.com/docs/frameworks/nuxt/quickstart), checked 28 October 2026.

Do you use Vue without Nuxt? See the [Vue cookie consent banner](/consent-banner/vue).

## Mount the banner in app.vue

Add `ConsentRoot` and a preferences link to `app/app.vue`. The module registers the components and composables, so you write no imports and no `app.use()`.

```vue
<template>
  <ConsentRoot />
  <NuxtPage />
  <footer>
    <ConsentDialogLink>Privacy settings</ConsentDialogLink>
  </footer>
</template>
```

Keep `ConsentRoot` outside `<NuxtPage />`, so it stays mounted across navigation. The components bring their own styles.

## Hold scripts until consent

Register each tag in `app/app.config.ts`. With `loadMode: 'after-consent'`, PostHog waits for measurement permission.

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

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

Keep `scripts` out of `nuxt.config.ts`. The module options there go to the browser as JSON, and JSON drops functions. Remove every other loader for PostHog, so it loads once and only through c15t.

[Nuxt scripts and embeds](https://c15t.com/docs/frameworks/nuxt/scripts) covers more vendors, iframes and the network blocker. The [c15t integrations](https://c15t.com/docs/integrations/overview) list the helper for each vendor.

## Move from @nuxt/scripts or nuxt-gtag

`@nuxt/scripts` loads a registry script when its own trigger fires, whatever the visitor chose in c15t. Move every tracking vendor to the c15t `scripts` array. You can keep `@nuxt/scripts` for necessary scripts.

The `nuxt-gtag` plugin adds `gtag.js` to every page when the app loads, so c15t cannot gate it. Replace it with the c15t Google tag helper. The [Nuxt scripts guide](https://c15t.com/docs/frameworks/nuxt/scripts) maps each registry script to its helper.

## Hold embeds until consent

Wrap third party iframes in `ConsentGate`. The iframe is not in the page until the visitor allows its category.

```vue
<template>
  <ConsentGate category="measurement">
    <iframe
      src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y"
      title="YouTube video"
      loading="lazy"
      allowfullscreen
    />
    <template #placeholder>
      <p>Allow measurement to load this YouTube video.</p>
      <ConsentDialogLink>Choose video permissions</ConsentDialogLink>
    </template>
  </ConsentGate>
</template>
```

Nuxt registers the components in `app/components` for you. Render `<VideoEmbed />` where the video goes.

## Google Consent Mode v2 in Nuxt

The Google tag helper sends the Consent Mode v2 default before your config and an update after each choice. It ships in `@c15t/integrations`.

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

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

Add the helper to the `scripts` array in `app/app.config.ts`, next to PostHog.

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. Google then gets 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, the module resolves each visitor from the bundled policy and the location headers of the request. A page render does not wait for Inth. [Nuxt geography headers](https://c15t.com/docs/frameworks/nuxt/geography-headers) lists the headers it reads.

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

## Keep the banner in the first HTML

The server resolves the policy for each visitor, so the banner is part of the first HTML. The browser bundle carries no policy and no resolver.

Prerendered and cached routes work with the same setup. On those routes, the browser resolves the visitor after hydration. For `nuxt generate` on a static host or `ssr: false`, see [Nuxt rendering](https://c15t.com/docs/frameworks/nuxt/rendering#deploy-to-static-hosting).

## Check that consent gates work

Build with `nuxt build`, start the server with `node .output/server/index.mjs` and open a private window with the Network tab open. Test under an opt in policy, such as the EU policy.

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. When you turn on measurement in Privacy settings, PostHog loads.
4. When you turn measurement off, the page reloads without PostHog.

Choices post to your Inth project. Check that Inth has a record for each one. The [c15t verify consent guide](https://c15t.com/docs/guides/verify-consent) has the full release checklist.

## 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).

## Cookie consent banners for other frameworks

The same banner and the same Inth project work in other stacks. See the setup for [Next.js](/consent-banner/nextjs), [React](/consent-banner/react), [TanStack Start](/consent-banner/tanstack-start), [Vue](/consent-banner/vue), [Astro](/consent-banner/astro), [Svelte](/consent-banner/svelte), [SvelteKit](/consent-banner/sveltekit), [HTML](/consent-banner/html) and [JavaScript](/consent-banner/javascript).

[See how the Inth consent banner works](/consent-banner).

## Questions about Nuxt consent

### Is there a separate Nuxt package?

No. `c15t/vue` is the Nuxt module. Add it to `modules` in `nuxt.config.ts`.

### Why do scripts go in app.config.ts?

Module options in `nuxt.config.ts` go to the browser as JSON, and JSON drops functions. `app/app.config.ts` keeps the vendor helpers intact.

### Can I keep @nuxt/scripts?

Keep it only for necessary scripts. It loads registry scripts on its own trigger, whatever the visitor chose. Move tracking vendors to the c15t `scripts` array.

### Does it work with prerendered or static pages?

Prerendered and cached routes work with the same setup. For `nuxt generate` or `ssr: false`, see [Nuxt rendering](https://c15t.com/docs/frameworks/nuxt/rendering#deploy-to-static-hosting).

### 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.

### Does it show a different banner in each region?

Yes. Set a policy for each region in your Inth project. c15t applies the policy that matches the visitor’s location. [See regional policies](/consent-banner/regions).

### Does it work in a Vue app without Nuxt?

Yes, with the Vue plugin. See the [Vue cookie consent banner](/consent-banner/vue).

### Can I run it without a backend?

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