Fixing Slow LCP on Pages With Embedded Video
Video makes landing pages and tutorials better and makes LCP worse, in ways that depend on how the video is embedded. A self-hosted hero video's LCP is usually its poster image, which browsers discover late and often load at low priority. A YouTube or Vimeo iframe is not an LCP candidate at all, but it drags in a megabyte of player code and several third-party connections that compete with whatever is the LCP element. And an autoplaying background video downloads several megabytes during the load, on every visit, often on mobile data.
This guide fixes LCP on two kinds of page on a product marketing site built with Astro: a landing page with a self-hosted hero video, and 140 tutorial pages with YouTube embeds near the top. It belongs to Largest Contentful Paint Optimization for Static Sites.
Symptoms
| Page | LCP p75 mobile | LCP element | Page weight |
|---|---|---|---|
| Landing (hero video) | 3.4 s | video poster | 6.8 MB (video autoplay) |
| Tutorials (YouTube embed) | 2.9 s | h1 or first paragraph | 1.6 MB |
On the landing page the poster was slow. On tutorials the LCP element was text, which should have been fast — but the YouTube iframe's resources were competing with the CSS and web font that the text needed.
Fix 1: Make the Poster Fast
The hero used <video autoplay muted loop playsinline poster="/hero-poster.jpg">. The poster was a 380 KB JPEG at 2560 pixels wide, discovered only when the parser reached the video element, and fetched at default (low-ish) priority. Three changes:
- Encode the poster properly. AVIF and WebP at the widths actually displayed, generated by
astro:assets. The mobile poster became 34 KB. - Make it an image the browser prioritises. The visual poster is now an
<img>withfetchpriority="high"behind the video, and the video has noposterattribute; the video fades in over it once it can play. The image is the LCP element and loads first. - Preload it in the head for the hero breakpoint, as in Reducing LCP from Hero Images on Static Sites.
<div class="hero-media">
<Picture src={poster} widths={[480, 960, 1600]} sizes="100vw" formats={['avif', 'webp']}
alt="" fetchpriority="high" loading="eager" class="hero-poster" />
<video class="hero-video" muted loop playsinline preload="none" data-src="/media/hero-720.mp4"></video>
</div>
Fix 2: Load the Video After LCP, Lighter
The hero video autoplayed a 6.1 MB 1080p MP4 on every visit, including phones. It now:
- Has
preload="none"and nosrcuntil after the page has loaded and the browser is idle; a small script sets the source then. - Picks a source by viewport width: a 720p, 1.2 MB AV1/H.264 pair for desktops, and on phones nothing at all — the poster image stays.
- Respects
prefers-reduced-motionby never loading the video for those readers. - Respects the Save-Data header and
navigator.connection.saveData.
const v = document.querySelector('.hero-video');
const ok = matchMedia('(min-width: 48em)').matches && !matchMedia('(prefers-reduced-motion: reduce)').matches
&& !navigator.connection?.saveData;
if (ok) requestIdleCallback(() => { v.src = v.dataset.src; v.play().then(() => v.classList.add('playing')); });
The video fades in over the poster with an opacity transition, which does not count as layout shift.
Fix 3: Facades for YouTube Embeds
Each tutorial embedded a YouTube video near the top with the standard iframe. Before the reader did anything, the page loaded the player's JavaScript, its CSS, several thumbnails and connections to four Google domains — 1.1 MB on mobile. That competed with the page's own CSS and font, delaying the text that was the LCP element.
The embeds were replaced with a facade: a static thumbnail and a play button, rendered at build time, which load the real player only when clicked. The site uses lite-youtube-embed, with the thumbnail self-hosted and optimised at build time rather than fetched from YouTube:
<lite-youtube videoid={id} playlabel={`Play: ${title}`}
style={`background-image: url(${thumb.src})`}></lite-youtube>
On click, it loads the iframe with autoplay=1 so one click plays the video. Lazy-Loading YouTube Embeds on Static Sites covers the facade in detail; the same approach works for Vimeo with lite-vimeo.
Fix 4: Reserve Space and Keep Captions
Both the hero and the facades reserve their space with aspect-ratio: 16 / 9, so nothing shifts when the player or video arrives. And the facade keeps accessibility intact: the play button has an accessible name including the video title, and each tutorial links to a transcript, which also gives search engines the video's content as text.
Self-Hosting Video Files
The hero video files were moved from the site's own static output to a video-specific path served with byte-range support and long caching, because static hosts differ in how well they handle range requests for large media. Encoding used two renditions — AV1 at 1.2 MB for browsers that support it and H.264 at 1.9 MB as fallback — listed as <source> elements in order, at 720p and a low bitrate suitable for a muted background loop. Encoding the loop at 24 frames per second instead of 60 halved the file size with no visible difference for this content. For longer product videos, the site uses a video platform with adaptive streaming rather than self-hosting, embedded through the same facade pattern so the player still loads only on click.
Testing Video Pages
A Lighthouse CI run on the landing page and three tutorials asserts that the LCP element is an image or text, never a video or iframe, and that no request to youtube.com or googlevideo.com happens before interaction. A Playwright test clicks a facade and checks that the player loads and starts playing within three seconds, so the facade cannot silently break playback.
Measured Impact
| Measure | Before | After | Tool |
|---|---|---|---|
| Landing LCP p75, mobile | 3.4 s | 1.6 s | web-vitals RUM |
| Landing page weight, mobile | 6.8 MB | 0.4 MB | DevTools |
| Tutorial LCP p75, mobile | 2.9 s | 1.7 s | web-vitals RUM |
| Tutorial weight before play | 1.6 MB | 0.5 MB | DevTools |
| Video plays per tutorial view | 31% | 30% | YouTube Analytics |
| CLS from video areas | 0.04 | 0 | web-vitals attribution |
Video plays per view barely changed: readers who wanted to watch clicked the facade just as they had clicked the player.
Pitfalls & Rollback
- Relying on the
posterattribute for the LCP image. It is discovered late; use a prioritised image. - Autoplaying large video on phones. Show the poster; load video only where it adds value.
- Standard iframes for third-party video. Use facades; the player loads on click.
- Forgetting reduced motion and Save-Data. Respect both.
- Rollback: the facade and the hero component are independent; either can return to the previous markup without affecting the other.
Conclusion
Video slows LCP through a late, heavy poster, a large autoplaying file loaded during the critical path, and third-party players that compete with the page's own resources. A prioritised, optimised poster image, video loaded only after LCP and only where it helps, and facades for YouTube embeds took the landing page from 3.4 to 1.6 seconds and tutorials from 2.9 to 1.7 seconds on mobile, without reducing how often readers watched.
FAQ
Can a video be the LCP element?
The video element itself counts through its poster image, and in current Chrome versions through the first painted frame of the video. In practice, a hero video's LCP is usually its poster image, so a large, unoptimised or late-discovered poster delays LCP. An iframe embed such as YouTube is not an LCP candidate, but the thumbnail inside it is not either, so the LCP element becomes whatever else is large.
Should a hero video autoplay?
Autoplaying muted video is allowed by browsers but costs bandwidth and main-thread time on every visit. If it carries real information, keep it but deliver a small, compressed file with a fast poster. If it is decorative, a static image or a short animated AVIF is lighter, and readers who prefer reduced motion should get the still image.
How much does a YouTube embed slow a page?
A standard YouTube iframe loads around 1 megabyte of JavaScript and other resources and several third-party connections before the reader presses play. On the pages in this guide, replacing embeds with a facade that loads the player on click cut total page weight by 1.1 megabytes per embed and improved LCP by about 0.8 seconds on mobile.
Should the poster image be preloaded?
If the poster is the LCP element, yes, with fetchpriority high, or by making it discoverable early in the HTML. The browser discovers a video's poster only when it parses the video element, and may give it low priority. A preload or an img placed in the markup as the visual poster ensures it loads first.
Related
- Parent: Largest Contentful Paint Optimization for Static Sites — LCP causes and fixes.
- Lazy-Loading YouTube Embeds on Static Sites — the facade in detail.
- Reducing LCP from Hero Images on Static Sites — hero image techniques.
- Fixing CLS From Late-Loading Embeds — reserving space for embeds.
- Fetchpriority Beyond the Hero Image — priority hints for other resources.
Filed underFixes