Video on your website without slowing it down
How to add video without hurting speed: where to host it, how to encode it small, how to load it late with a poster or facade, and what to check.
In this guide
What video costs
The 2025 Web Almanac found that video files are the largest of all file types on the pages it measured, from 7 KB at the 10th percentile to about a gigabyte at the top. Among home pages that loaded video, the median page loaded 288 KB of it on desktop and 512 KB on mobile. At the 90th percentile the figures were about 5 MB on desktop and 6.1 MB on mobile. The Almanac warns about its own figures: browsers often fetched the same video more than once with different range headers, which inflated the byte counts and seemed worse on mobile. Read the numbers as a sense of scale.
Bytes are only part of the cost:
- Script. A player is code. A player from another company is third-party code. See third-party scripts.
- Largest Contentful Paint. The poster image of a video, or the first frame of one that plays by itself, can be the LCP element. See Largest Contentful Paint.
- Layout shift. MDN recommends an explicit width and height on every video to avoid layout shift. See image width and height.
Do you need the video?
| What you want | A lighter choice |
|---|---|
| A short silent loop, like a GIF | A muted, looping video file. See replacing animated GIFs with video |
| Mood behind a heading | A still image, or a short loop that starts after the page has painted |
| To explain a product or service | A video the visitor chooses to play, behind a poster |
| A long talk, lesson or webinar | A video platform or a streaming service, not your own server |
A picture costs far fewer bytes than a clip. Keep video for the jobs where movement or sound really helps.
Where to host it
| Option | What you get | What you take on |
|---|---|---|
| Your own MP4 or WebM file | No third-party code, and full control of the player and of what loads | The bandwidth, and the job of encoding the file well. Best for short clips |
| A video platform's embed | The platform stores the video, makes several qualities and runs the player | An iframe with its own scripts and requests. Put a facade in front of it |
| A streaming service | Adaptive streaming | A player and a service to choose |
MDN describes adaptive streaming as quality that changes in real time with the visitor's bandwidth. It names HLS and MPEG-DASH as the main formats, and notes that cloud services will convert your media to both. For a long video that many people watch, that is a better fit than one big file.
Make a self-hosted video small
The main levers on the size are the frame size, the quality setting, the frame rate and the length. MDN's codec guide says that shrinking the frame before encoding can make a video much smaller. Its example is a 1080p video reduced to 720p, which can look better than the original encoded at full size with the quality cut to reach a size target. It adds that more motion and more noise mean larger files, and that 24 frames per second and above looks smooth.
These two commands were tested with FFmpeg 6.1. They resize to 1280 pixels wide, set 24 frames per second and drop the sound:
# MP4 (H.264) with the index at the front of the file
ffmpeg -i source.mov -vf "scale=1280:-2,fps=24" -c:v libx264 -crf 28 -pix_fmt yuv420p -an -movflags +faststart intro.mp4
# WebM (VP9)
ffmpeg -i source.mov -vf "scale=1280:-2,fps=24" -c:v libvpx-vp9 -crf 38 -b:v 0 -an intro.webmFor a video with sound, replace -an with -c:a aac in the MP4 command and with -c:a libopus in the WebM command. MDN's recommendations pair MP4 and H.264 with AAC audio, and WebM with Opus.
What the options do:
- `scale=1280:-2` sets the width and lets FFmpeg work out the height. As its documentation says,
-2keeps the picture's shape and makes that side a multiple of 2, which the common H.264 format needs. The GIF guide explainsyuv420pin replacing animated GIFs with video. - `-crf` is the quality setting. On a generated test clip, higher numbers gave smaller files with both codecs. Try a few values on your own footage and keep the smallest file that still looks right.
- `-an` turns the audio off. A silent loop needs no sound track.
- `-movflags +faststart` moves the file's index, the
moovatom, to the front. FFmpeg's documentation says it is normally written at the end and that moving it to the start is better for playback. It needs a second pass and is off by default.
To check the order in an MP4, run ffprobe -v trace intro.mp4 2>&1 | grep -m2 -E "type:'(moov|mdat)'". The first line should name moov.
Offer both files. MDN's first suggestion for typical websites is WebM with AV1, with Safari on older Apple devices as the exception. It calls VP9 widely supported and says MP4 with H.264 works in every major browser. The browser takes the first source it can play, so list the WebM first and the MP4 last.
Format is not the main lever. In the Almanac's data, the median WebM file on desktop home pages was larger than the median QuickTime or MPEG file. What sits inside the file, its size, length and quality, matters more than the wrapper.
Mark it up
<video controls preload="none" width="1280" height="720"
poster="/img/intro-poster.jpg">
<source src="/video/intro.webm" type="video/webm; codecs=vp9">
<source src="/video/intro.mp4" type="video/mp4">
<track kind="captions" src="/video/intro.en.vtt" srclang="en" label="English">
<p>Your browser cannot play this video. <a href="/video/intro.mp4">Download it</a>.</p>
</video>- `poster` is the image shown while the video downloads. Treat it like any image: right size, compressed, a modern format.
- `width` and `height` reserve the space.
- `preload` is a hint. MDN lists
none,metadataandauto, says the default differs between browsers, and says the specification advisesmetadata. Browsers may ignore it, andautoplaytakes precedence. web.dev adds that fetching starts only after the HTML has been loaded and parsed. With several videos on a page,noneplus a poster keeps them from competing with each other. - `track` adds captions. MDN says videos should have captions and a transcript. Transcripts help people who need more time to review the audio at their own pace.
- The text inside is shown only to browsers that cannot play video.
- No `autoplay` with sound. Browsers block it, and visitors dislike it. For a silent loop, see the GIF guide.
MDN's delivery guide notes that some mobile browsers may ignore autoplay and preload. Make the poster work on its own.
WordPress's Video block has a preload setting that defaults to metadata, per its block definition. WordPress core adds the loading attribute to the img and iframe tags it prints, but its helper for that supports only those two tags, so set preload for video yourself.
Load it late
An embedded player. Do not put the player on the page until someone asks for it. A facade is a static picture with a play button. Lighthouse's documentation describes the pattern in three steps: add the facade on load, preconnect when the pointer moves over it, and swap in the real player on a click. Its 2020 example showed a facade weighing 3 KB in place of a player weighing 540 KB. Measure your own embed.
Shopify's Dawn theme does this in its video section. The poster is a button. The YouTube or Vimeo iframe, or Shopify's own video, sits inside a template element and is added to the page when the button is pressed. The template element's content is not rendered by default, MDN says.
<div class="video-facade" style="aspect-ratio: 16 / 9">
<button type="button" aria-label="Play video: How we speed up a website">
<img src="/img/intro-poster.jpg" width="1280" height="720" alt="" loading="lazy">
</button>
<template>
<iframe src="https://player.example.com/embed/VIDEO_ID?autoplay=1"
title="How we speed up a website" allow="autoplay; encrypted-media"
allowfullscreen></iframe>
</template>
</div>
<script>
document.querySelectorAll('.video-facade').forEach((box) => {
box.querySelector('button').addEventListener('click', () => {
const frame = box.querySelector('template').content.firstElementChild.cloneNode(true);
box.replaceChildren(frame);
frame.focus();
});
});
</script>I ran this page in headless Chromium. Before the click only the page and the poster were requested. After it, the player address was requested and the frame took focus. The button is a real button with a label, so a keyboard works. The fixed shape stops the page from jumping. The third-party scripts guide shows a version that builds the iframe in script.
An iframe that must be on the page. MDN says loading="lazy" on an iframe defers loading until it nears the viewport, and only when JavaScript is enabled. The Almanac, quoting web.dev, puts the saving at around 500 KB for a YouTube embed, and found no iframe loading setting on 91.5% of desktop pages.
A video file. Use the poster and preload="none". For a muted loop that should start when it scrolls into view, use the observer script in the GIF guide. See also lazy loading.
Not for the first screen. A video at the top of the page should load at once. It may be your LCP element, so keep its poster small.
Serve it well
- Range requests. Players ask for a video in pieces. MDN calls range requests useful for media players that support random access. A server that supports them says so with an
Accept-Rangesheader, whichcurl -I https://example.com/video/intro.mp4shows. In the Network panel, one video can appear as several requests with the status 206 Partial Content, one for each piece the browser asked for. - Caching. A video rarely changes. Give each version its own file name and a long cache life. See HTTP caching headers.
- No more compression. MP4 and WebM are already compressed. See text compression.
- The right type. MDN warns that when the server sends the wrong MIME type, a video may not show or may show a gray box with an X. The types are
video/mp4andvideo/webm. - A CDN. A network of servers near your visitors helps most with large files. See what a CDN is.
A background video, with care
A looping video behind a heading is the heaviest way to decorate a page. The Almanac lists background videos among the things that can quickly balloon page size. If you keep one:
- Use a muted file with no audio track, a small frame, and a short loop.
- Give it a poster that looks right alone. On a phone, or for a visitor who asked for less motion, show only the poster.
- If it sits in the first screen, check whether it becomes the LCP element.
- Give it a pause control if it plays for more than five seconds. See the GIF guide.
This script adds the video only on wide screens for people who have not asked for reduced motion. I ran it in headless Chromium: at 1200 pixels wide the WebM was requested and played, and at 400 pixels, or with reduced motion on, only the poster was requested.
<video muted loop playsinline preload="none" width="1280" height="720"
poster="/img/hero-poster.jpg" data-hero></video>
<script>
const hero = document.querySelector('video[data-hero]');
const wide = matchMedia('(min-width: 900px)').matches;
const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (wide && !calm) {
hero.innerHTML = '<source src="/video/hero.webm" type="video/webm; codecs=vp9">' +
'<source src="/video/hero.mp4" type="video/mp4">';
hero.load();
hero.play().catch(() => {});
}
</script>How to check
- Count the bytes before play. Load the page with the Network panel open and filter by Media. Only the poster should load until a visitor asks for the video, unless it is a first-screen clip. See the Network panel.
- Find the LCP element. In PageSpeed Insights or Lighthouse, check whether the video or its poster is the element. See how to fix a slow LCP.
- Look for layout shift when the video or embed appears.
- Check the headers of the video request:
Content-Type,Accept-RangesandCache-Control. - Throttle the network and watch what the page does on a slow connection. Then try a real phone. See testing on real phones.
- Look at the third-party rows. An embed can add several requests to other hosts.
Mistakes to avoid
- A heavy video above the fold with no poster.
- An embed that loads at once though few visitors press play.
- A full-size source file put on the page.
- No `width` and `height`.
- `autoplay` with sound, which browsers block.
- An MP4 with its index at the end, which delays the start.
- A wrong MIME type or a server with no range support.
- No captions or transcript.
- One autoplaying hero video for every device.
Check your own page
Run the free audit and look at the largest files and the LCP element. Then open the Network panel, filter by Media, reload, and list every video file with its size. For each one, ask whether a visitor needs it before pressing play.
Sources
- Page Weight chapter, Web Almanac 2025: video sizes by percentile and format, and the note on duplicate range downloads.
- MDN: the video element, track element, template element, iframe element, audio and video delivery, video codec guide, HTTP range requests and media container formats.
- Fast playback with audio and video preload on web.dev: how
preloadbehaves. - Lazy load third-party resources with facades in the Lighthouse documentation.
- Shopify's Dawn theme: the video section and the
DeferredMediaelement in global.js. - FFmpeg documentation for the
scalefilter, and the formats documentation forfaststart. - WordPress: the video block definition and media.php in WordPress core.
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Speed optimization · Book a call