---
title: Embeds
description: Keep YouTube videos, maps and other iframes out of a Next.js page
  until their consent category is allowed, with ConsentGate or the iframe
  blocker in ConsentRoot.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Pick a method

|Method|Use it when|
|--|--|
|`ConsentGate`|You render the iframe in a React component and want a placeholder until the visitor allows its category.|
|The iframe blocker|The iframe comes from markup you do not render with a component, such as CMS or Markdown content.|

Both keep the iframe's `src` out of the page until the category is allowed, so
the embed's host receives no request before consent.

## Gate an embed with ConsentGate

Wrap the iframe in `ConsentGate` in a Client Component:

```tsx title="components/video-embed.tsx"
'use client';

import { ConsentGate } from 'c15t/next';

export const VideoEmbed = () => (
	<ConsentGate category="measurement">
		<iframe
			className="video-frame"
			sandbox="allow-scripts allow-same-origin allow-presentation"
			src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y"
			title="YouTube video"
			allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
			allowFullScreen
		/>
	</ConsentGate>
);
```

Import `VideoEmbed` into any page or Server Component under the layout that
mounts `ConsentRoot`; only this file needs `'use client'`. The iframe stays out
of the page until the visitor allows measurement, and a placeholder with a
button that opens the preference dialog takes its place.

Pick the category that matches what the embed does. The example uses
measurement for YouTube because its player measures views. A map or chat
widget usually belongs under functionality or experience.

What the server HTML contains depends on how your layout passes consent state.
[ConsentGate](/docs/frameworks/next/components/consent-gate) covers the
default, awaited and Pages Router layouts, the placeholder and its props.

## Gate an iframe with the iframe blocker

`ConsentRoot` runs the iframe blocker in the browser by default. Give an
iframe `data-src` instead of `src`, and a `data-category`:

```html title="Markup from your CMS"
<iframe
  data-src="https://www.google.com/maps/embed?pb=YOUR_EMBED_ID"
  data-category="functionality"
  title="Store map"
></iframe>
```

The server HTML has no `src`, so nothing loads before hydration. After
hydration, c15t sets `src` from `data-src` when the category is allowed and
removes `src` when the category is withdrawn. It watches the page, so iframes
added by client-side navigation are handled too. Add `data-vendor` with a
vendor ID to also block the iframe while the visitor has turned that vendor
off.

To turn the blocker off, pass `options={{ iframeBlocker: false }}` to
`ConsentRoot`.

## Verify the embeds

Open the production build in a private window with DevTools open, under a
policy that asks for consent.

1. View the page source. No iframe has a `src` pointing at
   `youtube-nocookie.com` or `google.com/maps`, and the Network panel shows no
   request to them.
2. Allow the embed's category and save. The iframe loads.
3. Withdraw the category and save. The page reloads without the embed.

The [YouTube](/docs/integrations/youtube) and
[Google Maps](/docs/integrations/google-maps) guides cover sizing and titles.
