Video Optimization Expert
Default Workflow
- Identify the role of each video: hero/background ambient loop, product demo, content/tutorial, testimonial, animated-GIF replacement, or third-party embed (YouTube/Vimeo).
- Choose the delivery mechanism by role and length: short silent loops as progressive MP4/WebM, anything over ~30s or with quality tiers as HLS/adaptive streaming, platform-hosted content behind a click-to-load facade.
- Fix the highest-impact path first: autoplay video competing with LCP, missing poster, unsized players causing CLS, heavy player JS, eager-loaded below-fold video, or multi-MB GIFs that should be video.
- Keep source quality high and delivery cheap: upload a high-bitrate master to the host/CDN, let it transcode the ladder; never re-encode an already-compressed download.
- Verify with evidence: network waterfall (what loads before first paint), poster/LCP element in DevTools, rendered player dimensions, and playback on a throttled mobile profile.
When a repo/app is available, make the patch instead of only giving advice. Prefer local framework conventions over generic snippets.
Fast Audit Checklist
- LCP: Above-fold video has a
poster (optimized image, not a video frame fetched late); the poster or a sibling image is the LCP candidate, not a spinner or black frame.
- CLS: Player/
<video> has explicit dimensions or CSS aspect-ratio before media loads.
- Autoplay: Ambient video is
muted autoplay loop playsinline with no controls; browsers block unmuted autoplay, so never rely on it.
- Preload:
preload="none" + poster for below-fold or click-to-play; preload="metadata" for likely-played; only streamed first segments preload for hero video.
- Weight: No GIFs over ~500KB (convert to MP4/WebM); silent videos have the audio track stripped; bitrate matches display size.
- Embeds: YouTube/Vimeo below the fold load as a facade (thumbnail + play button), not a full iframe at page load.
- Accessibility: Content videos have captions/transcripts; ambient video respects
prefers-reduced-motion; controls are keyboard reachable.
- SEO: Meaningful videos have VideoObject JSON-LD (some hosts, including Sirv, inject it automatically).
Decision Matrix
| Situation |
Preferred Action |
| Hero/background ambient loop |
Short (5-15s) muted MP4 (H.264) + WebM source, muted autoplay loop playsinline, poster, prefers-reduced-motion fallback to the poster. |
| Animated GIF anywhere |
Replace with <video muted autoplay loop playsinline> or animated WebP/AVIF for tiny clips; 10-50x smaller. |
| Product demo / content video |
HLS via a video host/CDN (Sirv, Mux, Cloudflare Stream, self-hosted hls.js) with poster, controls, preload="metadata". |
| Long-form or variable networks |
Adaptive HLS ladder; never a single 1080p progressive MP4. |
| YouTube/Vimeo embed |
Facade pattern (thumbnail + play button, swap to iframe on click); saves 0.5-1MB+ of third-party JS per embed. |
| Rotating product video |
Consider converting to a 360 spin (../sirv-360-spin/SKILL.md) - lighter, interactive, no play button. |
| Existing Sirv account |
Upload to Sirv; automatic HLS ladder + Sirv Media Viewer embed. Read sirv-video.md. |
| Video is the LCP element |
Ensure poster renders instantly (preload it if hero); or stream with a tiny preloaded first segment. |
Codec & Format Defaults
| Use Case |
Default |
| Universal compatibility |
H.264 (AVC) MP4, High profile, AAC audio |
| Better compression, wide support |
VP9 WebM or HEVC (Safari) as a second <source> |
| Best compression, modern targets |
AV1 (check hardware decode on mobile before committing) |
| Silent loops |
Strip audio track entirely (ffmpeg -an) - saves weight and enables autoplay reliably |
| Streaming |
HLS with 2-4 rung ladder (e.g. 360p/480p/720p/1080p); hosts generate this automatically |
Quality starting points: H.264 CRF 21-25 (lower = better), VP9 CRF 30-34, AV1 CRF 28-34. For ambient background video, go aggressive (CRF 26-28 H.264) - it sits behind content.
Core Patterns
Background/ambient hero video
<video class="hero-video" autoplay muted loop playsinline
poster="hero-poster.jpg" preload="none"
width="1920" height="1080" aria-hidden="true">
<source src="hero.webm" type="video/webm">
<source src="hero.mp4" type="video/mp4">
</video>
@media (prefers-reduced-motion: reduce) {
.hero-video { display: none; }
.hero { background-image: url(hero-poster.jpg); }
}
Preload the poster (it is likely your LCP), not the video.
GIF replacement
<video autoplay muted loop playsinline width="480" height="270"
aria-label="Description of the animation">
<source src="clip.mp4" type="video/mp4">
</video>
Click-to-load YouTube facade
Render a thumbnail (https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg) with a play button; inject the iframe (with autoplay=1) only on click. Use an existing web component like lite-youtube-embed rather than hand-rolling when the project allows dependencies.
When To Read References
- Web delivery details: encoding recipes (ffmpeg), HLS setup, lazy loading, embed facades, measurement: web-video.md
- Sirv video: upload formats, automatic HLS ladder, 512MB limit, SMV embed markup and
video.* options, thumbnails/posters, video-to-spin: sirv-video.md
Sibling skills: ../image-optimization/SKILL.md for posters/thumbnails as images, ../sirv-media-viewer/SKILL.md for galleries mixing video with images/spins, ../sirv-360-spin/SKILL.md for spin conversion, ../sirv-api/SKILL.md for video2spin/spin2video jobs.
Red Flags
- Autoplaying video without
muted and expecting it to play.
- A hero
<video> with no poster - users see a black box while segments load and LCP fires late.
- Serving one large progressive MP4 to all devices when the host could stream HLS.
- Full YouTube/Vimeo iframes loading at page load for below-fold embeds.
- Multi-MB animated GIFs.
- Lazy-loading logic that also lazy-loads the poster of an above-fold video.
- Shipping audio tracks in decorative videos.
- Re-encoding an already-lossy download as a "master".
Verification
- Network waterfall: only the poster (and at most one small first segment) loads before interaction for hero video; nothing video-related loads for below-fold embeds until scroll/click.
- LCP element is the poster or an intentional image, and its time is within budget on throttled mobile.
- No layout shift when the player initializes (compare with DevTools CLS overlay).
- Playback works on iOS Safari (
playsinline), Android Chrome, and desktop; muted loops actually autoplay.
prefers-reduced-motion shows the static fallback.
- For HLS: quality switches under throttling instead of stalling.
1---2name: video-optimization3description: Expert guidance on video optimization for web performance. Use when adding, auditing, or improving video on websites - hero/background videos, product demo videos, autoplay loops, GIF-to-video conversion, HLS/adaptive streaming, video posters and thumbnails, lazy loading video, YouTube/Vimeo embed performance, video Core Web Vitals (LCP/CLS/INP), video SEO/schema, or Sirv video hosting and streaming. Covers MP4/WebM/HLS delivery, H.264/VP9/AV1 codec choice, preload strategies, muted autoplay rules, facades for third-party players, and video-to-spin workflows.4---56# Video Optimization Expert78## Default Workflow9101. Identify the role of each video: hero/background ambient loop, product demo, content/tutorial, testimonial, animated-GIF replacement, or third-party embed (YouTube/Vimeo).112. Choose the delivery mechanism by role and length: short silent loops as progressive MP4/WebM, anything over ~30s or with quality tiers as HLS/adaptive streaming, platform-hosted content behind a click-to-load facade.123. Fix the highest-impact path first: autoplay video competing with LCP, missing poster, unsized players causing CLS, heavy player JS, eager-loaded below-fold video, or multi-MB GIFs that should be video.134. Keep source quality high and delivery cheap: upload a high-bitrate master to the host/CDN, let it transcode the ladder; never re-encode an already-compressed download.145. Verify with evidence: network waterfall (what loads before first paint), poster/LCP element in DevTools, rendered player dimensions, and playback on a throttled mobile profile.1516When a repo/app is available, make the patch instead of only giving advice. Prefer local framework conventions over generic snippets.1718## Fast Audit Checklist1920- **LCP:** Above-fold video has a `poster` (optimized image, not a video frame fetched late); the poster or a sibling image is the LCP candidate, not a spinner or black frame.21- **CLS:** Player/`<video>` has explicit dimensions or CSS `aspect-ratio` before media loads.22- **Autoplay:** Ambient video is `muted autoplay loop playsinline` with no controls; browsers block unmuted autoplay, so never rely on it.23- **Preload:** `preload="none"` + poster for below-fold or click-to-play; `preload="metadata"` for likely-played; only streamed first segments preload for hero video.24- **Weight:** No GIFs over ~500KB (convert to MP4/WebM); silent videos have the audio track stripped; bitrate matches display size.25- **Embeds:** YouTube/Vimeo below the fold load as a facade (thumbnail + play button), not a full iframe at page load.26- **Accessibility:** Content videos have captions/transcripts; ambient video respects `prefers-reduced-motion`; controls are keyboard reachable.27- **SEO:** Meaningful videos have VideoObject JSON-LD (some hosts, including Sirv, inject it automatically).2829## Decision Matrix3031| Situation | Preferred Action |32| --- | --- |33| Hero/background ambient loop | Short (5-15s) muted MP4 (H.264) + WebM source, `muted autoplay loop playsinline`, poster, `prefers-reduced-motion` fallback to the poster. |34| Animated GIF anywhere | Replace with `<video muted autoplay loop playsinline>` or animated WebP/AVIF for tiny clips; 10-50x smaller. |35| Product demo / content video | HLS via a video host/CDN (Sirv, Mux, Cloudflare Stream, self-hosted hls.js) with poster, controls, `preload="metadata"`. |36| Long-form or variable networks | Adaptive HLS ladder; never a single 1080p progressive MP4. |37| YouTube/Vimeo embed | Facade pattern (thumbnail + play button, swap to iframe on click); saves 0.5-1MB+ of third-party JS per embed. |38| Rotating product video | Consider converting to a 360 spin (`../sirv-360-spin/SKILL.md`) - lighter, interactive, no play button. |39| Existing Sirv account | Upload to Sirv; automatic HLS ladder + Sirv Media Viewer embed. Read [sirv-video.md](references/sirv-video.md). |40| Video is the LCP element | Ensure poster renders instantly (preload it if hero); or stream with a tiny preloaded first segment. |4142## Codec & Format Defaults4344| Use Case | Default |45| --- | --- |46| Universal compatibility | H.264 (AVC) MP4, High profile, AAC audio |47| Better compression, wide support | VP9 WebM or HEVC (Safari) as a second `<source>` |48| Best compression, modern targets | AV1 (check hardware decode on mobile before committing) |49| Silent loops | Strip audio track entirely (`ffmpeg -an`) - saves weight and enables autoplay reliably |50| Streaming | HLS with 2-4 rung ladder (e.g. 360p/480p/720p/1080p); hosts generate this automatically |5152Quality starting points: H.264 CRF 21-25 (lower = better), VP9 CRF 30-34, AV1 CRF 28-34. For ambient background video, go aggressive (CRF 26-28 H.264) - it sits behind content.5354## Core Patterns5556### Background/ambient hero video5758```html59<video class="hero-video" autoplay muted loop playsinline60 poster="hero-poster.jpg" preload="none"61 width="1920" height="1080" aria-hidden="true">62 <source src="hero.webm" type="video/webm">63 <source src="hero.mp4" type="video/mp4">64</video>65```6667```css68@media (prefers-reduced-motion: reduce) {69 .hero-video { display: none; }70 .hero { background-image: url(hero-poster.jpg); }71}72```7374Preload the poster (it is likely your LCP), not the video.7576### GIF replacement7778```html79<video autoplay muted loop playsinline width="480" height="270"80 aria-label="Description of the animation">81 <source src="clip.mp4" type="video/mp4">82</video>83```8485### Click-to-load YouTube facade8687Render a thumbnail (`https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg`) with a play button; inject the iframe (with `autoplay=1`) only on click. Use an existing web component like `lite-youtube-embed` rather than hand-rolling when the project allows dependencies.8889## When To Read References9091- **Web delivery details:** encoding recipes (ffmpeg), HLS setup, lazy loading, embed facades, measurement: [web-video.md](references/web-video.md)92- **Sirv video:** upload formats, automatic HLS ladder, 512MB limit, SMV embed markup and `video.*` options, thumbnails/posters, video-to-spin: [sirv-video.md](references/sirv-video.md)9394Sibling skills: `../image-optimization/SKILL.md` for posters/thumbnails as images, `../sirv-media-viewer/SKILL.md` for galleries mixing video with images/spins, `../sirv-360-spin/SKILL.md` for spin conversion, `../sirv-api/SKILL.md` for `video2spin`/`spin2video` jobs.9596## Red Flags9798- Autoplaying video without `muted` and expecting it to play.99- A hero `<video>` with no poster - users see a black box while segments load and LCP fires late.100- Serving one large progressive MP4 to all devices when the host could stream HLS.101- Full YouTube/Vimeo iframes loading at page load for below-fold embeds.102- Multi-MB animated GIFs.103- Lazy-loading logic that also lazy-loads the poster of an above-fold video.104- Shipping audio tracks in decorative videos.105- Re-encoding an already-lossy download as a "master".106107## Verification108109- Network waterfall: only the poster (and at most one small first segment) loads before interaction for hero video; nothing video-related loads for below-fold embeds until scroll/click.110- LCP element is the poster or an intentional image, and its time is within budget on throttled mobile.111- No layout shift when the player initializes (compare with DevTools CLS overlay).112- Playback works on iOS Safari (`playsinline`), Android Chrome, and desktop; muted loops actually autoplay.113- `prefers-reduced-motion` shows the static fallback.114- For HLS: quality switches under throttling instead of stalling.