Skip to main content

Astro Scripts and embeds

Embeds

Why an embed needs gating

An iframe sends requests to its host as soon as it is in the page with a src, before any script can stop it. ConsentBanner does not block iframes you already have. Render an embed only while its category is allowed, and remove it when the visitor withdraws permission.

Astro has no consent gate component. Use one of these:

ApproachUse it when
A custom element that renders the iframeYou want a placeholder with a button in place of the embed
The iframe blocker, with data-category and data-srcYou have iframe markup to gate as it is

Gate an embed with a custom element

This component shows a placeholder with a preferences button until measurement is allowed. It adds the iframe once the visitor allows measurement, and removes it when permission is withdrawn. It also keeps the iframe out while the visitor has switched YouTube off in vendor consent.

The component reads client.isVendorAllowed('youtube'), so declare youtube in the vendors option of c15t() with category: 'measurement'. An undeclared vendor reads as not allowed, and the video never loads:

src/components/consent-video.astro
---
import { ConsentDialogLink } from 'c15t/astro/components';
---

<consent-video>
	<div data-video>
		<p>Allow measurement to load this YouTube video.</p>
	</div>
	<ConsentDialogLink>Open privacy settings</ConsentDialogLink>
</consent-video>

<script>
	import { getConsentClient } from 'c15t/astro/client';

	class ConsentVideo extends HTMLElement {
		dispose?: () => void;

		connect = () => {
			const client = getConsentClient();
			const container = this.querySelector('[data-video]');
			if (this.dispose || !client || !container) {
				return;
			}
			const render = () => {
				// Measurement is allowed and the visitor has not switched YouTube
				// off. An undeclared vendor is never allowed, so declare youtube.
				if (!client.isVendorAllowed('youtube')) {
					container.textContent =
						'Allow measurement to load this YouTube video. No video request is sent before permission.';
					return;
				}
				if (container.querySelector('iframe')) {
					return;
				}
				const frame = document.createElement('iframe');
				frame.src = 'https://www.youtube-nocookie.com/embed/czTksCF6X8Y';
				frame.title = 'YouTube video';
				frame.allowFullscreen = true;
				container.replaceChildren(frame);
			};
			render();
			this.dispose = client.subscribe(render);
		};

		connectedCallback() {
			// c15t boots from a module script, which can run after this one.
			document.addEventListener('DOMContentLoaded', this.connect, {
				once: true,
			});
			this.connect();
		}

		disconnectedCallback() {
			document.removeEventListener('DOMContentLoaded', this.connect);
			this.dispose?.();
			this.dispose = undefined;
		}
	}

	if (!customElements.get('consent-video')) {
		customElements.define('consent-video', ConsentVideo);
	}
</script>

Use it like any Astro component. How it works:

  • The iframe does not exist in the server HTML, so nothing loads before consent, even before the consent runtime starts.
  • client.subscribe(render) renders again on every consent change.
  • connectedCallback runs again when ClientRouter swaps in a page that contains the element, so the embed works across navigation.
  • The first connect() can run before c15t has started, so the element tries again on DOMContentLoaded.

Change the category, the iframe src and the placeholder text for other embeds. The YouTube and Google Maps guides use the same pattern with a reusable browser helper. Integrations lists the other vendors.

Gate existing iframe markup

The consent runtime includes an iframe blocker, on by default. Mark an iframe with a category and move its URL from src to data-src:

src/pages/contact.astro (partial)
<iframe
  data-category="marketing"
  data-src="https://www.google.com/maps/embed?pb=..."
  title="Office location"
></iframe>

When the category is allowed, the blocker copies data-src to src. When it is withdrawn, the blocker removes src again. data-vendor gates the iframe on one vendor as well. See vendor consent.

Always use data-src, never src, for a gated iframe. The browser starts loading a src from the HTML before the blocker runs, so an iframe with src in the markup loads before consent.

The blocker watches the whole document, so it also gates iframes on pages you reach with ClientRouter, which replaces <body> on each navigation.

Gate an embed on the server

On a server-rendered page, Astro.locals.c15t.snapshot.effectivePermissions tells you whether the visitor had allowed the category when the request arrived. Rendering the iframe on the server from it works for returning visitors. A visitor who allows the category on the page sees the embed only after the next navigation, so pair it with the custom element, or use the custom element alone. See Server API.

Check the embeds

  1. Open the page in a private window with DevTools Network open. There is no request to the embed's host, and no iframe from it in the Elements panel.
  2. Allow the embed's category from Privacy settings. The iframe appears and loads.
  3. Reload. The iframe loads again without a new choice.
  4. Withdraw the category and save. The page reloads, and the embed's host gets no request.

See Verify consent for the full checklist.