YouTube-Videos DSGVO-konform einbetten — dreistufige Consent-Box ohne Cookie
Eine leichtgewichtige YouTube-Embed-Komponente für Astro mit echter Einwilligung (einmalig / merken / extern), ohne Cookie und ohne Drittanbieter-Request vor dem Klick.
Ich wollte in einem Post ein YouTube-Video einbetten. Problem: der klassische <iframe src="youtube.com/embed/…"> öffnet eine Pipeline zu Google, noch bevor der Leser irgendwas geklickt hat — Cookies, Fingerprint, IP, und das alles ohne Einwilligung. DSGVO-technisch ein Lehrbuchverstoß.
Was ich gebraucht habe:
- Vor dem Klick: null Requests an Google. Kein Thumbnail, kein Script, gar nichts.
- Echte Einwilligung — der Klick auf „Video laden” ist die Zustimmung (Art. 6 Abs. 1 lit. a DSGVO).
- Alternativen — „einmalig anzeigen”, „zustimmen und merken”, oder einfach „nur den Link”. Kein Cookie, maximal ein
localStorage-Eintrag für die Einwilligungsentscheidung.
Rausgekommen ist eine kleine Astro-Komponente, die das Muster umsetzt, das ich in der Praxis bei zeit.de abgeguckt habe.
Die Komponente
src/components/YouTubeEmbed.astro — Kern:
---
import { getLocaleFromUrl, t } from '~/i18n/ui';
interface Props { id: string; title?: string }
const { id, title } = Astro.props;
const locale = getLocaleFromUrl(Astro.url);
const privacyHref = locale === 'de' ? '/datenschutz/' : '/en/privacy-policy/';
const youtubeUrl = `https://www.youtube.com/watch?v=${id}`;
---
<figure class="youtube-embed">
<div class="yt-consent" data-id={id} data-title={title ?? ''}>
<h3>{t(locale, 'youtube.externalContent')}</h3>
<p>{t(locale, 'youtube.description')}</p>
<p>
{t(locale, 'youtube.serviceName')}
{title && <><br /><strong>{title}</strong></>}
</p>
<div class="yt-consent-buttons">
<button type="button" data-remember="true">{t(locale, 'youtube.loadVideoRemember')}</button>
<button type="button">{t(locale, 'youtube.loadVideo')}</button>
<a href={youtubeUrl} target="_blank" rel="noopener noreferrer">{t(locale, 'youtube.watchOnYouTube')}</a>
</div>
<p class="footer">
{t(locale, 'youtube.consentFooter')}
<a href={privacyHref}>{t(locale, 'youtube.privacyLink')}</a>.
</p>
</div>
</figure>
Dazu ein kleines Script, das die Platzhalter-Box beim Klick gegen einen <iframe> auf youtube-nocookie.com tauscht:
<script>
const CONSENT_KEY = 'yt-consent';
function buildIframe(id, title, autoplay) {
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube-nocookie.com/embed/${id}?rel=0${autoplay ? '&autoplay=1' : ''}`;
iframe.allow = 'autoplay; encrypted-media; picture-in-picture; fullscreen';
iframe.allowFullscreen = true;
iframe.loading = 'lazy';
iframe.referrerPolicy = 'strict-origin-when-cross-origin';
if (title) iframe.title = title;
return iframe;
}
const hasConsent = (() => {
try { return localStorage.getItem(CONSENT_KEY) === '1'; } catch { return false; }
})();
document.querySelectorAll('.yt-consent').forEach((container) => {
const { id, title } = container.dataset;
if (!id) return;
if (hasConsent) {
container.replaceWith(buildIframe(id, title, false));
return;
}
container.querySelectorAll('button').forEach((button) => {
button.addEventListener('click', () => {
if (button.dataset.remember === 'true') {
try { localStorage.setItem(CONSENT_KEY, '1'); } catch {}
}
container.replaceWith(buildIframe(id, title, true));
});
});
});
</script>
Drei wichtige Details:
youtube-nocookie.comstattyoutube.com— der „erweiterte Datenschutzmodus”. Setzt erst bei der Wiedergabe Daten, keine Personalisierungs-Cookies.localStoragestatt Cookie — die Einwilligungsentscheidung wird nur lokal im Browser gespeichert und nie an einen Server geschickt. Zählt nach ePrivacy als „unbedingt erforderlich” (es speichert die Einwilligung selbst, kein Tracking).- Auto-Replace bei
hasConsent— wer „merken” geklickt hat, sieht bei Folgebesuchen direkt den iframe, aber ohneautoplay(niemand mag Videos, die ungefragt losspielen).
i18n
Strings in src/i18n/ui.ts — je Locale:
'youtube.externalContent': 'Externer Inhalt',
'youtube.description': 'An dieser Stelle findest du einen externen Inhalt von youtube.com, der den Artikel ergänzt.',
'youtube.serviceName': 'YouTube Video Player (youtube.com)',
'youtube.loadVideo': 'Einmalig anzeigen',
'youtube.loadVideoRemember': 'Zustimmen und merken',
'youtube.watchOnYouTube': 'Auf YouTube ansehen',
'youtube.consentFooter': 'Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittanbieter übermittelt werden. Mehr dazu in der',
'youtube.privacyLink': 'Datenschutzerklärung',
getLocaleFromUrl(Astro.url) liest die Sprache aus dem Pfad — fertig, läuft automatisch DE/EN korrekt.
CSP anpassen
Meine strikte Caddy-CSP blockt jeden externen iframe. Der Embed braucht deswegen eine gezielte Freigabe:
- frame-ancestors 'none';
+ frame-src https://www.youtube-nocookie.com; frame-ancestors 'none';
img-src muss nicht erweitert werden, weil der Platzhalter keine Bilder von Google lädt. Kein Thumbnail, kein Google-Request vor Consent — das ist der Witz an der ganzen Konstruktion.
Referrer-Policy: unsichtbarer Stolperstein
Beim ersten Test: Error 153 — Fehler bei der Konfiguration des Videoplayers. Auch bei absolut einbettbaren Videos.
Ursache: meine seitenweite Referrer-Policy: no-referrer. YouTube prüft beim Embed per Referrer-Header, von welcher Domain der iframe kommt. Ohne Referrer keine Verifikation → generischer Error 153.
Fix auf iframe-Ebene: referrerPolicy = 'strict-origin-when-cross-origin'. YouTube sieht jetzt nur noch https://adrian-altner.de, keine Pfade — Privacy-Kompromiss vertretbar, Verifikation funktioniert.
Datenschutzerklärung
Ergänzt um einen eigenen Abschnitt „Video-Einbettungen (YouTube)”: Beschreibung der dreistufigen Lösung, Hinweis auf den localStorage-Eintrag yt-consent=1 (kein Cookie, nur lokal, nur Einwilligungsentscheidung), Rechtsgrundlage Art. 6 Abs. 1 lit. a DSGVO, Drittlandtransfer USA über Standardvertragsklauseln.
Demo
So sieht’s live aus — kein Request an Google, bevor du einen Button drückst:
Öffne die Netzwerk-Tab deines Browsers vor dem Klick: Du wirst keinen einzigen Request zu google.com, ytimg.com oder googlevideo.com sehen. Erst nach dem Klick lädt der iframe — und auch dann nur auf youtube-nocookie.com.
Fazit
Keine externe Consent-Library, kein Banner-Popup, kein Cookie. Eine Komponente (~100 Zeilen Astro), ein Script-Block, zwei CSP-Zeilen in Caddy, ein Eintrag in der Datenschutzerklärung. Für ein paar gelegentliche Video-Embeds auf einem Blog der richtige Größenordnungs-Kompromiss zwischen „vollständige Einwilligungsinfrastruktur” und „einfach ignorieren”.