Image, Video & Font Optimization
On a typical web page, images account for 50-70% of total bytes. Add video and fonts, and media easily exceeds 80%. Optimizing these assets is the single highest-impact performance work you can do.
Image Optimization
Format Selection
| Format | Best For | Compression | Browser Support |
|---|---|---|---|
| AVIF | Photos, illustrations | Best (30-50% smaller than WebP) | Chrome, Firefox, Safari 16+ |
| WebP | Photos, illustrations | Excellent (25-35% smaller than JPEG) | Universal |
| JPEG | Photos (fallback) | Good | Universal |
| PNG | Transparency, screenshots | Lossless | Universal |
| SVG | Icons, logos, illustrations | Vector (scales perfectly) | Universal |
Responsive Image Strategy
<picture>
<source
srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
type="image/avif"
/>
<source
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
type="image/webp"
/>
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
alt="Mountain landscape"
width="800"
height="450"
loading="lazy"
decoding="async"
/>
</picture>Loading Priorities
<!-- LCP image: load immediately with high priority -->
<img src="hero.avif" fetchpriority="high" loading="eager" alt="Hero" />
<!-- Below-fold images: lazy load -->
<img src="product.avif" loading="lazy" decoding="async" alt="Product" />Preventing Layout Shift
Always set explicit dimensions or use CSS aspect-ratio:
img {
max-width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}Image CDN Pipeline
Modern image CDNs (Cloudinary, imgix, Vercel Image Optimization) handle:
- Format auto-negotiation via
Acceptheader - Responsive resizing via URL parameters
- Quality optimization (perceptual quality vs file size)
- CDN caching at edge locations
<img
src="https://cdn.example.com/hero.jpg?w=800&q=80&f=auto"
alt="Hero image"
/>Video Optimization
Encoding Best Practices
| Codec | Container | Use Case |
|---|---|---|
| AV1 | WebM | Best compression, limited hardware encoding |
| VP9 | WebM | Good compression, wide support |
| H.264 | MP4 | Universal fallback |
Lazy Loading Video
<video
preload="none"
poster="thumbnail.jpg"
width="720"
height="405"
>
<source src="video.webm" type="video/webm; codecs=vp9" />
<source src="video.mp4" type="video/mp4" />
</video>Background Video Pattern
Replace GIFs (which are massive) with muted autoplay video:
<video autoplay muted loop playsinline preload="metadata">
<source src="animation.webm" type="video/webm" />
<source src="animation.mp4" type="video/mp4" />
</video>A 10-second WebM is typically 80-90% smaller than the equivalent GIF.
Video Facade Pattern
Don't embed YouTube/Vimeo iframes eagerly â they load 500KB+ of scripts:
function VideoFacade({ videoId }: { videoId: string }) {
const [loaded, setLoaded] = useState(false);
if (loaded) {
return (
<iframe
src={`https://www.youtube.com/embed/${videoId}?autoplay=1`}
allow="autoplay"
title="Video player"
/>
);
}
return (
<button onClick={() => setLoaded(true)}>
<img
src={`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`}
alt="Play video"
loading="lazy"
/>
</button>
);
}Font Optimization
Font Loading Strategy
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
}font-display Values
| Value | Behavior | Best For |
|---|---|---|
swap | Show fallback immediately, swap when loaded | Body text |
optional | Brief block, use if ready, skip if not | Non-critical text |
fallback | Short block (100ms), swap period (3s) | Balance flash vs invisible |
block | Long invisible period | Icon fonts (if you must) |
Preloading Critical Fonts
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
/>crossorigin is required even for same-origin fonts â the font loading spec mandates it.
Subset Fonts
If you only need Latin characters, subset the font to remove unused glyphs:
pyftsubset Inter.woff2 \
--output-file=Inter-latin.woff2 \
--flavor=woff2 \
--unicodes=U+0000-00FFA full Inter variable font is ~300KB; Latin-only subset is ~30KB.
Variable Fonts
One file replaces multiple weight/style files:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
h1 { font-weight: 700; }
p { font-weight: 400; }Fallback Font Matching
Reduce CLS from font swap by matching fallback metrics:
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 107%;
}
body {
font-family: 'Inter', 'Inter Fallback', sans-serif;
}size-adjust and the override properties fine-tune the system font to match the web font's metrics, preventing text reflow on swap.
Measuring Impact
| Metric | What It Measures | Target |
|---|---|---|
| LCP | Largest Contentful Paint (usually hero image) | < 2.5s |
| CLS | Cumulative Layout Shift (image/font reflow) | < 0.1 |
| Total Weight | Page bytes transferred | < 1MB first load |
| Image Weight | % of total bytes from images | Minimize with modern formats |
Interview Signal
Senior candidates demonstrate:
- Format fluency â AVIF > WebP > JPEG chain, when to use each, browser negotiation
- Loading strategy â
fetchpriorityfor LCP images,loading="lazy"for below-fold, explicit dimensions for CLS - Font optimization â
font-display, preloading, subsetting, variable fonts, fallback metric matching - Video awareness â Facade pattern, codec selection, replacing GIFs with muted video
- Measurement â Connecting optimizations to Core Web Vitals metrics, not just "smaller files"