# Video on landing pages without killing your LCP

> Hero videos and YouTube embeds are the fastest way to a 6-second LCP. Learn what counts as the LCP element for video, the poster and facade patterns that keep it under 2.5 s, plus WordPress settings.

Updated 2026-09-25 · Speed & Core Web Vitals · HTML version: https://getreport.app/guides/video-on-landing-pages-without-killing-lcp

A video above the fold is the most expensive thing a landing page can ask a phone to do before the visitor has read a word. A YouTube embed pulls in hundreds of kilobytes of scripts before a frame appears; a self-hosted autoplay clip competes with the page for the same bandwidth its hero image needs. Both push Largest Contentful Paint past 2.5 s and, done carelessly, add layout shift on top. This guide shows what the browser counts as the LCP element when video is involved, and the three patterns that let you keep the video and pass the metric.

## Quick answer

- Give every video a **poster image** and make that poster the LCP element: a compressed WebP or AVIF sized to the viewport, with `fetchpriority="high"` where it is an `<img>` and no lazy loading.
- **YouTube and Vimeo above the fold**: never load the real player on page load. Show a thumbnail with a play button and load the iframe on click (a "facade"). Below the fold, `loading="lazy"` on the iframe is enough.
- **Self-hosted autoplay loops**: muted, `playsinline`, under 1 MB, no audio track, and off for visitors who asked for reduced motion.
- Run the [speed test](https://getreport.app/tools/speed-test) and read the LCP element line: if it names the video or its poster, that file decides the metric.

## Why video hurts LCP

### What the browser counts

Largest Contentful Paint is the time at which the biggest image, text block or video in the viewport has finished rendering. For a `<video>` element, Chrome uses whichever comes first: the poster image once it has loaded, or the first frame of the video once it is presented, which since 2023 includes autoplaying video. In practice that means one of three things becomes your LCP element:

- **The poster image**, if there is one and it is big. This is the case you want, because a poster is a plain image and every LCP image technique applies.
- **The first video frame**, for an autoplaying clip without a poster. The browser has to download enough of the file to decode a frame, which on a 4 MB MP4 over 4G is seconds.
- **Something else entirely** when the player is an iframe. A YouTube embed is not content the browser can measure, so LCP falls to the largest headline or image around it, while the embed's scripts delay everything by competing for the network and the main thread. Google's [LCP documentation](https://web.dev/articles/lcp) lists which elements are candidates.

### Where the seconds go

A standard YouTube iframe loads the player page, its stylesheets, several scripts and the thumbnail before the play button responds, typically well over 500 KB across a dozen requests, most of it JavaScript that has to parse and run on the phone. Vimeo is lighter but the same shape. None of it is your content, and all of it happens before or alongside your hero image.

A self-hosted `<video autoplay>` is cheaper in scripts and worse in bytes. The browser starts fetching the file immediately, at the same time as the hero image, the fonts and the stylesheet. On a slow connection everything arrives later, and the LCP image finishes after the video's first frame did.

Late players add a third cost. A YouTube iframe without dimensions, or a video widget that injects its player after a script loads, pushes the content below it down, and that jump is Cumulative Layout Shift.

## How getReport checks it

> **Free tool:** [Website speed test](https://getreport.app/tools/speed-test): Lighthouse lab results and real-user Core Web Vitals for any page, mobile and desktop, with a filmstrip, a request waterfall and a fix for every slow part. Free, no signup.

The speed module runs Lighthouse on a throttled mobile connection and names the LCP element under the lab table, so you can see whether the poster, the video or the headline decided the metric:

![The speed test panel for a landing page with a hero video: the lab table with the Largest Contentful Paint row rated poor, the LCP element line naming the element, and the field bars beside it](https://getreport.app/guides/img/video-on-landing-pages-without-killing-lcp/speed-panel.webp "The LCP element line under the lab table says which element the browser measured; for a video hero it is usually the poster image or the first frame.")

> **Check: Largest Contentful Paint.** LCP is when the biggest thing on screen — usually the hero image or headline — finishes loading. Visitors judge "is this site slow?" on it, and Google uses it for ranking.
>
> 1. Find the LCP element (named in your report) and make it lighter, earlier or both — a compressed WebP/AVIF, sized to the screen, without lazy loading.
> 2. Preload it with <link rel="preload" as="image"> or add fetchpriority="high" on the <img>.
> 3. Cut what comes before it — render-blocking CSS/JS and slow server responses push LCP back.

The third-party finding is where a YouTube or Vimeo embed shows up, listed by company with the bytes transferred and the main-thread time it blocked:

> **Check: Third-party code weight.** Tags, widgets and embeds from other companies run on your visitors' phones at your page's expense. Over 250 KB or 250 ms of blocking is a sign they dominate the load.
>
> 1. List what each third party gives you; remove the ones nobody looks at.
> 2. Load the rest after interaction or with a facade (a static thumbnail for YouTube, a click-to-load chat button).
> 3. Move tags into a tag manager that fires them late, not in <head>.

Two checks from the best-practices module, which loads the page in Chromium, cover the self-hosted case:

> **Check: No video starts playing automatically.** Autoplaying video downloads megabytes before the visitor asked for anything, drains phone batteries and makes the page move while people try to read it.
>
> 1. Remove the autoplay attribute and show a poster image with a play button instead.
> 2. If a background video must autoplay, keep it muted, under 1 MB and use prefers-reduced-motion to disable it.

The autoplay check counts `<video>` elements that carry the `autoplay` attribute after the page has rendered, so a player added by a script is counted too. It is a low-weight warning by design: a muted background loop under 1 MB is acceptable, and the finding is there to make you check the size.

> **Check: Total image weight.** Images are usually the heaviest part of a page. Over 1.5 MB in total, or a single image over 500 KB, adds seconds on mobile and costs visitors real data.
>
> 1. Resize images to the size they are shown at (a 400 px wide photo does not need a 4000 px file).
> 2. Convert to WebP or AVIF and compress to about 80 % quality; CMS plugins and CDNs can do this automatically.

The image weight finding counts every image the browser downloaded while loading the page. A 900 KB poster shows here as the largest image; a poster served at 4000 px for a 400 px phone screen is the same mistake as an oversized hero photo, and the [LCP guide](https://getreport.app/guides/fix-largest-contentful-paint) has the sizing steps.

## Step by step

### 1. Make the poster the LCP element

Every `<video>` gets a `poster`. Export it as WebP or AVIF at the size it is displayed (1200 px wide is enough for phones and most laptops; 100–200 KB), and give it `width` and `height` so the space is reserved before anything loads:

```html
<video
  poster="/media/hero-poster.webp"
  width="1280" height="720"
  preload="none"
  controls
  playsinline>
  <source src="https://getreport.app/media/hero.webm" type="video/webm">
  <source src="https://getreport.app/media/hero.mp4" type="video/mp4">
</video>
```

`preload="none"` tells the browser not to fetch any of the video until the visitor presses play; `preload="metadata"` fetches only the headers and duration, which is fine for a video with controls and a visible length. Either way the poster is the only thing that downloads on page load, so it is the LCP candidate, and it loads at normal image priority.

To get the poster fetched earlier, preload it from `<head>`; a `poster` attribute has no `fetchpriority`, so the preload is how you raise it:

```html
<link rel="preload" as="image" href="https://getreport.app/media/hero-poster.webp" fetchpriority="high">
```

An alternative that gives you full control is to render the poster as a normal `<img>` with a play button over it, and swap in the `<video>` on click. Then `fetchpriority="high"` goes straight on the `<img>`, `srcset` and `sizes` work as they do for any hero image, and nothing video-related loads until asked.

### 2. Autoplay loops: small, muted, inline, optional

Background loops can autoplay without hurting LCP if they are small enough not to compete with the poster:

```html
<video
  class="hero-loop"
  poster="/media/loop-poster.webp"
  width="1280" height="720"
  autoplay muted loop playsinline
  preload="metadata">
  <source src="https://getreport.app/media/loop.webm" type="video/webm">
  <source src="https://getreport.app/media/loop.mp4" type="video/mp4">
</video>
```

The rules that keep this cheap:

- **Under 1 MB.** A 6–10 s loop at 720p, no audio track, encoded at a low bitrate, lands at 500–900 KB. Anything longer belongs behind a play button.
- **`muted` and `playsinline`.** Browsers only autoplay muted video, and iPhones only play inline with `playsinline`; without it the clip opens full screen or not at all.
- **Still give it a poster.** The poster paints first and becomes the LCP element while the first frame is still decoding.
- **Respect reduced motion.** Visitors who set "reduce motion" in their OS asked for no moving backgrounds. Stop the loop and keep the poster:

```js
// After the DOM is ready
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  document.querySelectorAll('video.hero-loop[autoplay]').forEach((v) => {
    v.removeAttribute('autoplay');
    v.pause();
  });
}
```

### 3. YouTube and Vimeo: load a facade, not the player

A facade is a thumbnail and a play button that look like the player and load it only on click. The visitor who never plays the video (most of them) never pays for it. The lightest option is the `lite-youtube-embed` web component: one small script and stylesheet, the thumbnail from YouTube's image CDN, and the real iframe only after the click:

```html
<link rel="stylesheet" href="https://getreport.app/assets/lite-yt-embed.css">
<script src="https://getreport.app/assets/lite-yt-embed.js" defer></script>

<lite-youtube videoid="dQw4w9WgXcQ" playlabel="Play: Product tour"
  style="background-image: url('/media/tour-poster.webp');">
</lite-youtube>
```

Self-hosting the poster (the `background-image` line) lets you serve a compressed WebP instead of YouTube's JPEG and keeps the LCP element on your own domain. Without a library, a plain thumbnail and button with a few lines of JavaScript does the same:

```html
<button class="yt-facade" data-id="dQw4w9WgXcQ" aria-label="Play video: Product tour">
  <img src="https://getreport.app/media/tour-poster.webp" width="1280" height="720" alt="" fetchpriority="high">
</button>
<script>
  document.querySelectorAll('.yt-facade').forEach((btn) => {
    btn.addEventListener('click', () => {
      const iframe = document.createElement('iframe');
      iframe.src = `https://www.youtube-nocookie.com/embed/${btn.dataset.id}?autoplay=1`;
      iframe.width = 1280; iframe.height = 720;
      iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
      iframe.allowFullscreen = true;
      iframe.title = btn.getAttribute('aria-label');
      btn.replaceWith(iframe);
    });
  });
</script>
```

A real `<button>` keeps it keyboard-reachable; `youtube-nocookie.com` avoids setting cookies before the click, which matters for consent as well as speed. Set the same dimensions on the button's `<img>` and the iframe so nothing shifts when they swap.

### 4. Embeds below the fold: lazy-load the iframe

For a video the visitor has to scroll to, the browser's own lazy loading is enough and needs no script:

```html
<iframe
  src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
  width="1280" height="720"
  loading="lazy"
  title="Product tour"
  allowfullscreen></iframe>
```

`loading="lazy"` on iframes is supported in every current browser. The `width` and `height` keep the aspect ratio reserved; add `style="aspect-ratio: 16 / 9; width: 100%; height: auto"` in CSS for a fluid layout without shift. Never lazy-load an iframe or poster that is visible on load; that is the [lazy-loading mistake](https://getreport.app/guides/lazy-loading-done-right) that makes LCP worse.

### 5. Hosting and formats

Serve self-hosted video from the CDN, not from the web server's disk; a 40 MB file streaming from a shared host slows every other request on the site. Provide two encodings: H.264 MP4 plays everywhere, and a WebM (VP9 or AV1) source listed first is 30–50 % smaller for browsers that support it. Videos over about a minute should not be a single progressive file at all: use a service (Mux, Cloudflare Stream, Vimeo, Bunny Stream) that serves HLS or DASH, so the player fetches the first seconds at a bitrate the connection can handle.

For the loop itself, encode with a two-pass bitrate target rather than a quality setting, at 24 fps, and strip the audio track:

```bash
ffmpeg -i loop-source.mov -an -vf "scale=1280:-2,fps=24" -c:v libx264 -b:v 900k -pass 1 -f null /dev/null
ffmpeg -i loop-source.mov -an -vf "scale=1280:-2,fps=24" -c:v libx264 -b:v 900k -pass 2 -movflags +faststart loop.mp4
```

`-movflags +faststart` moves the file's index to the front so playback can start before the whole file is down.

## Platform notes

### WordPress

- **Video block** (block editor): the sidebar has Autoplay, Loop, Muted, Play inline, Preload (Auto, Metadata, None) and Poster image. Set Preload to None or Metadata and always choose a poster; the block outputs a plain `<video>`, so everything above applies.
- **Embed block** for YouTube and Vimeo: WordPress outputs the provider's iframe. Since WordPress 5.7, iframes in post content that have `width` and `height` get `loading="lazy"` automatically, which covers embeds below the fold but does nothing for one in the hero. For an above-the-fold embed use a facade plugin (WP YouTube Lyte and Lazy Load for Videos both replace the iframe with a thumbnail) or WP Rocket's "Replace YouTube iframe with preview image" under Media.
- **Elementor Video widget**: it has a Lazy Load switch in the video options that shows the thumbnail and loads the player on click, and a separate "Image Overlay" setting where you can supply your own compressed poster. Turn both on for hero videos.
- **Theme hero videos** set in the Customizer are usually `<video autoplay>` with the original upload as the source. Re-encode the file with the ffmpeg lines above before uploading, and add a poster.

### Shopify

The Video section in Online Store 2.0 themes accepts a Shopify-hosted file or a YouTube/Vimeo URL and lets you set a cover image; upload a compressed cover rather than relying on the generated frame. YouTube and Vimeo added this way load the provider's iframe, so place them below the fold or use a theme that supports a click-to-play thumbnail. Apps that inject video sliders are a third-party cost like any other app.

### Static sites and custom builds

Use the facade pattern from step 3 as a component, and self-host the poster through your image pipeline so it gets the same `srcset` and format conversion as every other image. Frameworks with an image component (Astro, Next.js, Nuxt) can render the poster `<img>` with `fetchpriority="high"` and correct dimensions out of the box.

## Verify

- The speed test's LCP element line names your poster image (or the headline, if that is bigger), not a video source and not a YouTube domain.
- The third-party finding no longer lists YouTube or Vimeo on page load; it reappears only after you click play, which the test never does.
- The autoplay finding either passes or lists one muted loop, and the image weight finding shows the poster under about 200 KB.
- In the waterfall of the speed panel, no `.mp4` or `.webm` request starts before the poster image finishes.
- The Core Web Vitals field bars, on the [Core Web Vitals checker](https://getreport.app/tools/core-web-vitals), move within a month: real visitors on slow connections are the ones the video was hurting most.

## Common mistakes

- **Autoplay video without a poster.** Symptom: LCP element is the video, LCP over 4 s on mobile. Fix: add a compressed poster and preload it.
- **Poster exported from the video frame at full resolution.** Symptom: image weight finding names a 1–2 MB PNG poster. Fix: WebP or AVIF at 1200 px, about 80 % quality.
- **YouTube iframe in the hero with `loading="lazy"`.** Symptom: nothing improves, because the iframe is in the viewport and loads immediately anyway. Fix: a facade for the hero, lazy loading only below the fold.
- **A facade that swaps in an iframe of a different size.** Symptom: CLS spike on click. Fix: identical `width` and `height` on the thumbnail and the iframe, or an `aspect-ratio` container around both.
- **Two lazy-video plugins, or a plugin plus the theme's own facade.** Symptom: two thumbnails, or a player that needs two clicks. Fix: keep one, deactivate the other, re-test.
