Skip to content
SlashClone

Embed YouTube videos without tracking cookies

@slashclone/privacy-friendly-youtube-embed

Embed YouTube videos on your site without loading anything from Google until the visitor presses Play or allows videos. A click-to-play placeholder with a local poster swaps in the youtube-nocookie.com player, with a keyboard-friendly button, no layout shift, CSP entries, and a Playwright test that proves no YouTube request happens first. React and HTML templates. Not legal advice.

SkillDevOpsv0.1.1 (latest)
Activity
New: be the first to try it
  1. 1// Adds the YouTube player to a Content Security Policy only when the site uses
  2. 2// embeds. A site with no videos should not allow the host at all.
  3. 3//
  4. 4// Usage, wherever you build your CSP header:
  5. 5// const csp = withYouTube(baseDirectives, { enabled: siteHasVideoEmbeds });
  6. 6// const header = serializeCsp(csp);
  7. 7
  8. 8export type CspDirectives = Record<string, string[]>;
  9. 9
  10. 10export type YouTubeCspOptions = {
  11. 11 /** True when at least one page renders a YouTube embed. */
  12. 12 enabled: boolean;
  13. 13 /**
  14. 14 * True only if you show YouTube thumbnails after consent. The templates in this
  15. 15 * kit use a local poster, so the default is false and img-src is untouched.
  16. 16 */
  17. 17 thumbnailsAfterConsent?: boolean;
  18. 18};
  19. 20const PLAYER = "https://www.youtube-nocookie.com";
  20. 21const THUMBNAILS = "https://i.ytimg.com";
  21. 23function add(csp: CspDirectives, directive: string, source: string): void {
  22. 24 const list = csp[directive] ?? ["'self'"];
  23. 25 if (!list.includes(source)) list.push(source);
  24. 26 csp[directive] = list;
  25. 29/** Returns a copy of `base` with the YouTube hosts added when enabled. */
  26. 30export function withYouTube(base: CspDirectives, options: YouTubeCspOptions): CspDirectives {
  27. 31 const csp: CspDirectives = Object.fromEntries(Object.entries(base).map(([k, v]) => [k, [...v]]));
  28. 32 if (!options.enabled) return csp;
  29. 33 // The iframe is the only thing the page itself loads. Scripts, media, and
  30. 34 // connections inside the player run under YouTube's origin, not your CSP.
  31. 35 add(csp, "frame-src", PLAYER);
  32. 36 if (options.thumbnailsAfterConsent) add(csp, "img-src", THUMBNAILS);
  33. 37 return csp;
  34. 40export function serializeCsp(csp: CspDirectives): string {
  35. 41 return Object.entries(csp)
  36. 42 .map(([directive, sources]) => [directive, ...sources].join(" "))
  37. 43 .join("; ");

Cookies that keep you signed in and remember these choices are always on. Everything else is off until you turn it on.