Embedding External Video

Putting YouTube (and others) on your page, responsibly

Basic YouTube embed

When you click "Share → Embed" on any YouTube video, the site hands you a ready-made <iframe> snippet. You paste it into your HTML and, without any server-side code, your visitor gets YouTube's full adaptive streaming player — multiple quality levels, captions, and mobile optimization — served from Google's infrastructure at no cost to you.

Anatomy of the embed code

The snippet YouTube generates looks like this (slightly simplified):

<iframe width="560" height="315" src="https://www.youtube.com/embed/aqz-KE-bpKQ" title="Big Buck Bunny — Blender Foundation" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen> </iframe>

Let's unpack each attribute:

Attribute Why it matters
src Uses /embed/VIDEO_ID — a different URL from the normal watch page. This URL is specifically for embedding.
title Required for accessibility. Screen readers announce the iframe by its title; without it, users hear something like "iframe" with no context.
frameborder Legacy attribute; set to "0" to remove the iframe border. In modern code you would do this with CSS (border: 0), but YouTube's snippet includes it for compatibility.
allow A Permissions Policy that grants the embedded frame access to specific browser features. Without autoplay listed, the player cannot autoplay even if instructed. Without picture-in-picture, PiP fails silently.
allowfullscreen Grants the iframe permission to call the Fullscreen API. Without this, the fullscreen button in the player does nothing.

Live demo

Below is a live embed of Big Buck Bunny (a short film released by the Blender Foundation under a Creative Commons licence — a reliable, always-available test target). It uses a .embed-16x9 wrapper; the responsive CSS is explained in the next section.

Tip: The video ID is the string after /embed/. You can get it from any YouTube URL: youtube.com/watch?v=aqz-KE-bpKQ. IDs are stable — they never change once a video is published.

Privacy-enhanced mode

By default, YouTube's player loads tracking scripts as soon as the iframe appears in the page — even before the user presses play. This can trigger GDPR consent requirements. YouTube offers a simple opt-out: swap youtube.com for youtube-nocookie.com in the embed URL. With that change, YouTube does not set cookies or run tracking code unless the user actually interacts with the player.

One-line change

<!-- Standard (tracking on page load) --> <iframe src="https://www.youtube.com/embed/aqz-KE-bpKQ" ...></iframe> <!-- Privacy-enhanced (tracking only on play) --> <iframe src="https://www.youtube-nocookie.com/embed/aqz-KE-bpKQ" ...></iframe>

The player looks and behaves identically. The only difference is when YouTube's cookies and analytics scripts are loaded. As a rule of thumb: always use youtube-nocookie.com unless you have a specific reason not to.

Tip: You can also append ?rel=0 to the embed URL to suppress "related videos" at the end of playback: …/embed/aqz-KE-bpKQ?rel=0. Multiple parameters are joined with &: ?rel=0&modestbranding=1.

Responsive embed

An <iframe> has a fixed pixel size by default — width="560" height="315" in YouTube's snippet. That works fine on a wide desktop, but on a phone it overflows its container or gets clipped. The fix is a CSS wrapper that uses aspect-ratio to lock the 16:9 shape and lets the iframe fill it absolutely.

The wrapper CSS

.embed-16x9 { position: relative; aspect-ratio: 16 / 9; max-width: 640px; /* optional upper bound */ } .embed-16x9 iframe { position: absolute; inset: 0; /* shorthand for top/right/bottom/left: 0 */ width: 100%; height: 100%; border: 0; }

The HTML

<div class="embed-16x9"> <iframe src="https://www.youtube-nocookie.com/embed/aqz-KE-bpKQ" title="Big Buck Bunny — Blender Foundation" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen> </iframe> </div>

How it works: the wrapper div has no explicit height — instead aspect-ratio: 16 / 9 calculates its height automatically from its width, which flows from the normal block layout. The iframe is then pulled out of flow (position: absolute) and stretched to fill the wrapper using inset: 0 (equivalent to top:0; right:0; bottom:0; left:0). Remove the width and height attributes from the <iframe> itself, or they will fight the CSS.

Older approach: Before aspect-ratio was widely supported, developers used a padding-top hack: padding-top: 56.25% (= 9/16 × 100%) on the wrapper with height: 0. You will still see this in older codebases. The aspect-ratio property (supported in all modern browsers since 2021) is cleaner and preferred today.

Performance — lazy loading and click-to-load

A YouTube embed loads a significant amount of JavaScript as soon as the iframe renders — potentially several hundred kilobytes. On a page with multiple embeds, or on a slow mobile connection, this meaningfully harms page-load time and Core Web Vitals scores.

loading="lazy"

The loading="lazy" attribute, the same attribute you can add to <img>, defers loading the iframe until it is near the viewport. Add it to any embed that starts below the fold:

<div class="embed-16x9"> <iframe src="https://www.youtube-nocookie.com/embed/aqz-KE-bpKQ" title="Big Buck Bunny — Blender Foundation" loading="lazy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen> </iframe> </div>

Click-to-load facade

For maximum performance — especially when a video is above the fold — consider replacing the iframe entirely until the user clicks play. The technique shows a static thumbnail image with a play button overlay. On click, JavaScript swaps the image for the real iframe. The page loads almost no YouTube code until the user signals intent.

<div class="yt-facade" id="facade-wrap" data-videoid="aqz-KE-bpKQ"> <img src="https://i.ytimg.com/vi/aqz-KE-bpKQ/hqdefault.jpg" alt="Big Buck Bunny thumbnail — click to play"> <div class="play-overlay"> <button class="play-btn" aria-label="Play Big Buck Bunny"> <svg width="24" height="24" viewBox="0 0 24 24"> <polygon points="5,3 19,12 5,21"/> </svg> </button> </div> </div> (function () { var wrap = document.getElementById('facade-wrap'); if (!wrap) return; wrap.querySelector('.play-btn').addEventListener('click', function () { var id = wrap.dataset.videoid; var iframe = document.createElement('iframe'); iframe.src = 'https://www.youtube-nocookie.com/embed/' + id + '?autoplay=1'; iframe.title = 'Big Buck Bunny — Blender Foundation'; iframe.allow = 'accelerometer; autoplay; clipboard-write; ' + 'encrypted-media; gyroscope; picture-in-picture; web-share'; iframe.allowFullscreen = true; iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:0'; // Replace the facade content with the live iframe wrap.innerHTML = ''; wrap.appendChild(iframe); }); }());

Note the ?autoplay=1 appended to the src — this is intentional here because the user just clicked play; without it the iframe would load and then sit paused. A complete, ready-to-use version of this pattern is in Example 02: YouTube Embed.

Live facade demo

The thumbnail below is not an iframe — it is a static image. No YouTube code loads until you click the play button.

Big Buck Bunny thumbnail — click to play
Click to load video

Vimeo

Vimeo works on the same principle. The embed URL uses player.vimeo.com/video/VIDEO_ID. Vimeo does not offer a -nocookie domain equivalent, but it does accept privacy settings configured on the video itself (e.g., "only embed on approved domains"). The same responsive wrapper and lazy-loading techniques apply.

<!-- Vimeo responsive embed --> <div class="embed-16x9"> <iframe src="https://player.vimeo.com/video/76979871" title="Big Buck Bunny — Vimeo" loading="lazy" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen> </iframe> </div>

Self-hosting vs embedding

At this point it is worth stepping back and comparing your two main options. Neither is universally better — the right choice depends on your priorities.

Platform embed (YouTube/Vimeo) Self-hosted <video>
Hosting & bandwidth Free — the platform handles CDN delivery You pay for storage and bandwidth
Adaptive streaming Built in — quality adjusts to connection speed Requires HLS/DASH setup (complex, needs server)
Privacy Third-party cookies/analytics (mitigated by -nocookie) Full control — no third-party code
Player customisation Limited — platform controls the UI Full control via the JS API (see Tutorial 06)
Content ownership Platform can take down the video; embed breaks You own and control the file
Discovery Video is indexed and may surface in platform search No platform discovery benefit

For most educational and commercial sites, embedding from YouTube or Vimeo is the pragmatic starting point — you get professional delivery for free. If privacy, branding control, or content ownership are priorities, self-hosting with <video> is the better path. See Tutorial 01: Media on the Web for the broader context, and Tutorial 03: The Video Element for the full self-hosting API.