A page with 30 YouTube iframe embeds loads 26MB of JavaScript and makes 467 network requests. Here's how I reduced that to 1.8MB and 42 requests on TopVideoHub.
The Problem
Each YouTube iframe embed loads:
- ~800KB of JavaScript
- 15+ network requests
- The full YouTube player interface
Multiply by 30 videos on a category page and you get a page that's unusable on mobile connections — exactly where most of our Asia-Pacific users browse.
The Facade Pattern
Replace each iframe with a static thumbnail and CSS play button. The real iframe loads only when clicked.
HTML Structure
<div class="video-grid">
<article class="video-card" data-video-id="dQw4w9WgXcQ">
<div class="video-facade">
<img src="https://i.ytimg.com/vi/dQw4w9WgXcQ/mqdefault.jpg"
alt="Video title here"
loading="lazy"
width="320" height="180"
decoding="async">
<button class="play-btn" aria-label="Play video">
<svg width="68" height="48" viewBox="0 0 68 48">
<path class="play-bg" d="M66.5 7.7s-.7-4.7-2.8-6.8C60.7-2.1 57.2-2.1 55.6-2.3 46.5-3 34-3 34-3s-12.5 0-21.6.7c-1.6.2-5.1.2-8.1 3.2C2.2 3 1.5 7.7 1.5 7.7S.8 13.3.8 18.8v5.2c0 5.5.7 11.1.7 11.1s.7 4.7 2.8 6.8c3 3.1 6.9 3 8.7 3.3 6.3.6 26.8.9 26.8.9s12.5 0 21.6-.8c1.6-.2 5.1-.2 8.1-3.1 2.1-2.1 2.8-6.8 2.8-6.8s.7-5.5.7-11.1v-5.2c0-5.5-.7-11.1-.7-11.1z" fill="red"/>
<path d="M27 33V11l18.4 11L27 33z" fill="#fff"/>
</svg>
</button>
</div>
<h3 class="video-title">Video title here</h3>
<p class="video-channel">Channel Name</p>
</article>
</div>
CSS
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
padding: 1rem;
}
.video-facade {
position: relative;
aspect-ratio: 16 / 9;
background: #0a0a0a;
cursor: pointer;
overflow: hidden;
border-radius: 8px;
}
.video-facade img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.2s;
}
.video-facade:hover img {
transform: scale(1.05);
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: none;
border: none;
cursor: pointer;
opacity: 0.8;
transition: opacity 0.2s;
padding: 0;
}
.video-facade:hover .play-btn {
opacity: 1;
}
.video-facade iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
JavaScript
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.video-facade').forEach(facade => {
facade.addEventListener('click', function handleClick() {
const card = this.closest('.video-card');
const videoId = card.dataset.videoId;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0`;
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
iframe.allowFullscreen = true;
iframe.loading = 'lazy';
// Remove thumbnail and play button, add iframe
this.innerHTML = '';
this.appendChild(iframe);
this.removeEventListener('click', handleClick);
});
});
});
Responsive Thumbnails
YouTube provides thumbnails at different sizes. Pick the right one:
function thumbnailUrl(string $videoId, string $size = 'mq'): string {
$sizes = [
'default' => 'default', // 120x90
'mq' => 'mqdefault', // 320x180
'hq' => 'hqdefault', // 480x360
'sd' => 'sddefault', // 640x480
'maxres' => 'maxresdefault', // 1280x720
];
$file = $sizes[$size] ?? $sizes['mq'];
return "https://i.ytimg.com/vi/{$videoId}/{$file}.jpg";
}
For grid cards, mqdefault (320x180, ~15KB) is the sweet spot — sharp enough on mobile, light enough for fast loading.
Performance Results
Measured on TopVideoHub category pages (30 videos):
| Metric | Before | After |
|---|---|---|
| Page Weight | 26.4 MB | 1.8 MB |
| Requests | 467 | 42 |
| LCP | 4.2s | 1.1s |
| CLS | 0.32 | 0.01 |
| FID | 380ms | 12ms |
The aspect-ratio: 16/9 CSS eliminates layout shift entirely. No more jumping content as thumbnails load.
This technique works on any site with multiple video embeds. The implementation is simple, the performance gains are massive, and it requires zero dependencies.
Top comments (0)