HTML2 min read

One embedded video cost me two seconds of load time

A YouTube embed pulls in about a megabyte before anyone presses play. Here is the lazy facade pattern, plus the sandbox and allow attributes that keep third party frames contained.

You add one video embed to a landing page. The Lighthouse score drops 18 points. The page now loads about a megabyte of JavaScript from a domain you do not control, before anyone has decided whether to watch anything.

An iframe is a whole second web page inside yours. It has its own document, its own scripts, and its own network cost.

Step one: stop loading it up front

<iframe
  src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
  title="Product demo, 3 minutes"
  loading="lazy"
  width="560"
  height="315"
></iframe>

Three things here.

loading="lazy" defers the load until the frame is near the viewport. For anything below the fold this is a one attribute win.

title is required for accessibility. A screen reader announces the frame by its title, and "iframe" with no name is a dead end. Describe the content, not the technology.

width and height reserve the space so the page does not jump when the frame arrives.

Step two: the facade, for embeds above the fold

Lazy loading does not help if the video is at the top of the page. Show an image, and only create the iframe when the user actually clicks.

<button type="button" class="video-facade" id="play">
  <img src="/thumb.jpg" width="560" height="315" alt="" />
  <span>Play the product demo, 3 minutes</span>
</button>
document.getElementById("play").addEventListener("click", (event) => {
  const frame = document.createElement("iframe");
  frame.src = "https://www.youtube-nocookie.com/embed/VIDEO_ID?autoplay=1";
  frame.title = "Product demo, 3 minutes";
  frame.allow = "autoplay; encrypted-media; picture-in-picture";
  frame.width = 560;
  frame.height = 315;
  event.currentTarget.replaceWith(frame);
});

The button is a real <button>, so it is keyboard reachable and announced properly. The thumbnail has alt="" because the button text already names the action.

This turns a megabyte on every page view into a megabyte only for people who watch. On a page where five percent of visitors press play, that is a large saving.

Step three: contain what the frame can do

sandbox removes capabilities and then you add back only what is needed:

<iframe
  src="https://example.com/widget"
  title="Pricing calculator"
  sandbox="allow-scripts allow-same-origin"
  loading="lazy"
></iframe>

An empty sandbox blocks scripts, forms, popups, top level navigation, and treats the content as a unique origin. Common tokens:

  • allow-scripts lets it run JavaScript
  • allow-same-origin keeps its own origin, needed for cookies and storage
  • allow-forms lets it submit forms
  • allow-popups lets it open windows

One warning that matters: allow-scripts together with allow-same-origin for a frame from your own origin lets the frame remove its own sandbox attribute. For untrusted content, serve it from a different origin.

allow is the other half, and it controls browser features rather than the document:

allow="camera 'none'; microphone 'none'; geolocation 'none'; fullscreen"

Deny by default, grant only what the embed genuinely needs.

Step four: check what it costs you

In DevTools, open the Network panel, filter by the third party domain, and load the page fresh. That number is what the embed costs every visitor.

Then decide. A support chat widget that loads 800KB on a marketing page is often better as a link to a contact form, and nobody misses it.

The referrer detail

By default the frame gets your full URL in the Referer header, including path and query string. On a page like /orders/1234/invoice, that leaks something. Set:

referrerpolicy="strict-origin-when-cross-origin"

Now the third party sees your domain, not the path.

Written by Rahul Mourya, full stack engineer. Spotted a mistake or want the follow-up post? Email me.

All posts
  • HTML3 min read

    srcset and sizes: the part everyone gets wrong

    srcset alone does not save bandwidth. Without a correct sizes attribute the browser assumes your image is the full page width and downloads the largest file anyway.

    • images
    • performance
    • responsive
  • HTML2 min read

    The viewport meta tag that breaks pinch to zoom

    user-scalable=no and maximum-scale=1 are copied into projects every day. They fail an accessibility audit, and the bug they were added to fix has another cause.

    • viewport
    • mobile
    • accessibility
  • HTML3 min read

    The autocomplete tokens that fix mobile checkout

    autocomplete is not on or off. It is a list of specific tokens that let phones fill an address in one tap. Getting them right is the cheapest conversion win in a form.

    • forms
    • mobile
    • autofill