Image optimization strategies
One route per technique, from unoptimized to fully optimized. Each renders the same 21-image dataset as a responsive grid; each grid item links to a large detail view (the LCP element). The Lighthouse harness (pnpm benchmark:optimg) measures the live deploy — warm runs, one dpl, mobile and desktop — to compare image bytes and LCP.
Strategies
- Naive
naivePlain <img>, full-size, no srcset/sizes/dimensions. The measurement floor. - Manual
manualHand-cut widths + srcset/sizes + baked blur over public/ files. The bash era, done right. - Auto
auto<Picture> generates formats, srcset, sizes, and width/height. Toil deleted. - Pixel-perfect
pixel-perfectsizes computed from layout tokens so the served file lands on the slot. - Skeleton
skeletonAuto plus a static grey box behind the slot — no image, no bytes, no decode. The cheap floor of the perceived-fill gradient. - LQIP
lqipAuto plus a getImage() blurred placeholder and a cache-guarded fade-in. - Cropped
croppedfit=cover with per-view aspect ratios: 4:3 thumb and 16:9 cover from one imported source — two distinct build outputs. - Final
finalThe production stack: LQIP placeholder + cache-guarded fade, over pixel-perfect token widths, with an optional per-image crop.
Measured results — desktop
| Strategy | LCP med (min–max) ms | CLS | Image bytes (KB) |
|---|---|---|---|
| naive | 605(541–690) | 0.004 | 9124 |
| manual | 375(365–435) | 0.000 | 888 |
| auto | 348(339–385) | 0.000 | 716 |
| pixel-perfect | 305(279–372)↑ | 0.000 | 222↑ |
| skeleton | 398(337–466) | 0.000 | 716 |
| lqip | 404(387–488) | 0.000 | 716 |
| cropped | 369(357–388) | 0.000 | 785 |
| final | 403(373–423) | 0.000 | 222 |
Warm-cache Lighthouse 13 medians — desktop, 5 runs each on one deploy. Image bytes are the deterministic ranking; LCP is the secondary confirmation — see the README.
Key findings
- Bytes wins go to pixel-perfect and final — 222 KB and 222 KB, ~69% under
auto(716 KB) and 98% undernaive. Exactsizes+widthsderived from layout tokens means the CDN serves the slot file with no slack. - LQIP does not improve LCP — its LCP (404 ms) runs ~56 ms over
auto(348 ms) at equal image bytes: the cost is decoding the placeholder, not the image. The above-fold LCP snaps now — the fade is exempted above the fold — so the placeholder never inflates the metric. LQIP's payoff is perceived speed: a filled slot instead of a white box while the real file streams. - final is the production pick — matches pixel-perfect on bytes (222 KB) while LQIP delivers instant visual feedback. The 98 ms LCP delta vs pixel-perfect is the cost of the LQIP layer (placeholder decode), not a fade — the above-fold image snaps.
Measured results — mobile
| Strategy | LCP med (min–max) ms | CLS | Image bytes (KB) |
|---|---|---|---|
| naive | 28926(28901–29103) | 0.257 | 9124 |
| manual | 6547(6528–6592) | 0.000 | 1148 |
| auto | 3789(3779–3817) | 0.000 | 597 |
| pixel-perfect | 4523(4487–4674) | 0.000 | 913 |
| skeleton | 3803(3792–3832) | 0.000 | 597 |
| lqip | 3816(3805–3868) | 0.000 | 597 |
| cropped | 3611(3567–3633)↑ | 0.000 | 585↑ |
| final | 4534(4504–4593) | 0.000 | 913 |
Warm-cache Lighthouse 13 medians — mobile, 5 runs each on one deploy. Image bytes are the deterministic ranking; LCP is the secondary confirmation — see the README.
Desktop vs mobile — same code, the slot flips
| Strategy | Desktop (KB) | Mobile (KB) |
|---|---|---|
| auto | 716 | 597 |
| pixel-perfect | 222 | 913 |
| final | 222 | 913 |
pixel-perfect is the lightest on desktop (316 px 3-col thumb) and among the heaviest on mobile (full-width hero at DPR 1.75), whileauto does the reverse — it under-sizes the big mobile slot. Bytes compare within a mode; each row is one strategy's own desktop→mobile shift.