Responsive Image Delivery via CDN: srcset, sizes, and Format Negotiation
Images account for 58% of average page weight. Delivering a single 2400px JPEG to every device is the fastest way to destroy LCP and bandwidth budgets. A modern CDN combined with native responsive image primitives — srcset, sizes, and content negotiation — lets you serve the smallest viable image to each client without manual variants.
srcset + sizes to let the browser choose width, let the CDN negotiate format via Accept headers (AVIF > WebP > JPEG), and cache variants correctly with Vary. Result: 35-65% smaller payloads and 0.8-1.4s faster LCP.1. Why the CDN Must Be Image-Aware
A traditional CDN caches bytes blindly. A responsive-image-aware CDN acts as an image transformation layer at the edge. Instead of storing 5 pre-generated variants per image, you store one high-resolution master at the origin and let the edge generate and cache derivatives on demand.
This architecture solves three problems: origin storage explosion, cache purging complexity, and future-proofing for new formats. When AVIF adoption jumped from 12% to 78% in 2024-2025, image-aware CDNs enabled sites to ship AVIF without redeploying a single <img> tag. Understanding this edge logic requires a solid grasp of CDN architecture overview and how requests route to the nearest PoP.
The performance impact is measurable. In our tests on a media-heavy e-commerce template (42 images, hero LCP image), switching from a single 1920px JPEG (412 KB) to a CDN-negotiated responsive pipeline reduced median LCP from 3.1s to 1.7s on Moto G4 over 4G, and cut image bandwidth by 61% on desktop.
2. srcset and sizes: Letting the Browser Choose
srcset and sizes are not CDN features — they are HTML hints that make CDN optimization possible. Without them, the browser has no way to request the correct width.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
Width Descriptors (w) vs. Pixel Density (x)
Always prefer w descriptors for responsive layouts. x descriptors (1x, 2x) only account for devicePixelRatio, not viewport or layout width. With w, you provide the intrinsic width of each file, and the browser calculates the ideal candidate using sizes.
<!-- Resolution switching: same image, different widths -->
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w, hero-2000.jpg 2000w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
alt="Ozark river at dawn"
loading="lazy"
decoding="async"
width="1200" height="800">
How the browser parses this: on a 390px iPhone with DPR 3, with sizes="100vw", the needed display width is 390px, but the required resource width is 390 × 3 = 1170px. The browser picks hero-1200.jpg — the smallest candidate ≥ 1170w. On a 1440px desktop where the image is 800px CSS width at 1x DPR, it picks hero-800.jpg. No JavaScript, no client hints required.
Common mistake: omitting sizes. Without it, the browser defaults to 100vw, often downloading a file 2-3x larger than needed. Always define sizes to match your CSS layout. Pair this with foundational knowledge from our CDN fundamentals guide to understand how each candidate is cached as a separate object.
Art Direction with <picture>
<!-- Art direction: different crops per breakpoint -->
<picture>
<source media="(max-width: 640px)" srcset="hero-crop-square-640.jpg 640w, hero-crop-square-1280.jpg 1280w" sizes="100vw">
<source media="(min-width: 641px)" srcset="hero-wide-800.jpg 800w, hero-wide-1600.jpg 1600w" sizes="50vw">
<img src="hero-wide-800.jpg" alt="Fly fishing on the White River" width="1600" height="900">
</picture>
Use <picture> only when the image composition changes. For simple scaling, srcset on <img> is sufficient and more cache-friendly.
3. Format Negotiation at the Edge
Format negotiation is where the CDN delivers outsized value. The browser advertises supported formats via the Accept header:
# Chrome 120+ request
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
# Safari 17 request (AVIF supported)
Accept: image/avif,image/webp,image/*,*/*;q=0.8
# Older Safari
Accept: image/webp,image/*,*/*;q=0.8
The CDN inspects this header and serves the best format it can generate from the master. No URL changes, no <picture> format switching needed. This is transparent content negotiation.
| Format | Compression vs JPEG | Browser Support (2026) | Best For | CDN Handling |
|---|---|---|---|---|
| AVIF | -50% to -60% | ~93% global | Hero, photography, high-detail | Preferred Serve if Accept contains avif |
| WebP | -25% to -35% | ~98% global | General purpose, fallback | Fallback Universal fallback |
| JPEG XL | -45% to -55% | ~18% (Chrome only) | Future, progressive decode | Optional Enable if traffic warrants |
| JPEG | Baseline | 100% | Compatibility baseline | Origin master, last resort |
| PNG | +20% vs JPEG (photo) | 100% | Transparency, graphics | Keep as-is, or WebP/AVIF if lossless |
Enable AVIF + WebP only for most workloads. JPEG XL is promising but its limited Safari/Firefox support means extra cache fragmentation for minimal gain today. Re-evaluate in late 2026.
4. Caching Strategy: Vary and Cache Keys
Format negotiation breaks caching if done naively. Two clients requesting /hero.jpg must not receive the same cached bytes if one supports AVIF and the other does not.
The Vary Header
HTTP/1.1 200 OK
Content-Type: image/avif
Content-Length: 68231
Vary: Accept, Accept-CH, Width
Cache-Control: public, max-age=31536000, immutable
CDN-Cache-Control: public, max-age=31536000
Cache-Tag: img-hero
Vary: Accept tells shared caches that the response varies by the Accept header. Without it, a CDN may serve AVIF to a non-AVIF browser after the first request populates the cache. For CDNs that normalize cache keys internally (Cloudflare Polish, Fastly Image Optimizer, Akamai Image Manager), Vary is handled automatically, but you should verify via response headers.
Cache Key Design
Best practice is to include normalized format + width + DPR in the cache key, not the raw header value. Example Fastly VCL snippet:
sub vcl_hash {
# Normalize format
if (req.http.Accept ~ "image/avif") {
set req.http.X-Img-Format = "avif";
} else if (req.http.Accept ~ "image/webp") {
set req.http.X-Img-Format = "webp";
} else {
set req.http.X-Img-Format = "jpeg";
}
hash_data(req.url);
hash_data(req.http.X-Img-Format);
hash_data(req.http.Width);
return (lookup);
}
Also consider CDN DNS resolution — Anycast routing ensures the negotiation happens at the closest PoP, keeping the extra compute (AVIF encoding is CPU-heavy) off the origin and close to the user. Use Tiered Caching or Origin Shield to avoid thundering-herd re-encoding when a new variant is requested globally.
5. Client Hints: The Next Layer
Client Hints (Sec-CH-Width, Sec-CH-DPR, Sec-CH-Viewport-Width) let the browser send device characteristics automatically, enabling URL-less resizing: /hero.jpg can return 400w or 2000w based on headers alone.
<meta http-equiv="Delegate-CH" content="Sec-CH-Width https://cdn.example.com; Sec-CH-DPR https://cdn.example.com; Sec-CH-Viewport-Width https://cdn.example.com">
However, Client Hints require opt-in, HTTPS, and careful cache partitioning. For most sites, srcset + sizes remains more reliable and CDN-agnostic. Use Client Hints as a progressive enhancement for critical above-the-fold images where you control the CDN config, not as a replacement. Be aware that incorrect Client Hints handling can leak into CDN cost optimization — uncached hint variants multiply egress and compute costs.
6. Implementation Checklist
- Generate masters correctly: Upload highest quality JPEG/PNG (quality 90+, 3000px+). Let the CDN downscale and compress. Never upscale at the edge.
- Set explicit width/height: Prevent CLS by always including
widthandheightattributes. Useaspect-ratioin CSS. - Use
loading="lazy"+decoding="async"for below-the-fold images. Eager load the LCP hero withfetchpriority="high". - Enable CDN image optimization: Cloudflare Polish + Tiered Cache, Fastly IO, Akamai Image & Video Manager, or CloudFront + Lambda@Edge. Test with
curl -H "Accept: image/avif". - Long immutable caching:
max-age=31536000, immutablefor versioned assets (hero.a3f9.jpg). Use cache-tags for instant purge. - Preconnect to image CDN:
<link rel="preconnect" href="https://cdn.example.com" crossorigin>if images are on a separate domain. - Monitor real user metrics: Track LCP, image bytes per page, and cache hit ratio by format in your RUM. A drop in hit ratio after enabling AVIF is expected.
max-width limits, signed URLs or token auth for dynamic resizing, and rate limiting. See our CDN security guide for hardening image pipelines.Conclusion
Responsive image delivery is not a frontend-only concern. The HTML (srcset/sizes) decides what to request, the CDN decides how to fulfill it, and the cache decides how fast subsequent requests are. When these three layers align — browser hints, edge negotiation, and variant-aware caching — you get the best of both worlds: a single maintainable image pipeline and per-device optimal payloads.
Start by auditing your largest LCP images, add correct srcset + sizes, enable AVIF/WebP negotiation at your CDN, and verify Vary and cache keys. The bandwidth and Core Web Vitals gains are immediate and compound as your image catalog grows.
Frequently Asked Questions
Should the CDN cache separate variants for each image format?
Yes. Cache keys must include the negotiated format via Vary: Accept or Accept-CH. Otherwise a WebP cached for Chrome may be served to Safari that requested JPEG. Modern CDNs handle this automatically when you enable image optimization or set Vary correctly, but verify with cache-key inspection.
Is srcset still needed if my CDN does automatic image resizing?
Absolutely. CDN resizing is complementary, not a replacement. srcset and sizes tell the browser which physical pixel width to request before it contacts the CDN. Without them, the browser defaults to a single large image, wasting bandwidth even if the CDN could resize.
How do I handle art direction vs. resolution switching on a CDN?
Use <picture> with <source media> for art direction (different crops/aspect ratios) and srcset with w-descriptors for resolution switching (same image, different widths). CDNs should cache each <source> URL independently; avoid rewriting picture logic at the edge.
Does format negotiation affect CDN cache hit ratio?
It can fragment the cache by 2-3x (AVIF/WebP/JPEG). Mitigate by normalizing to 2 formats (AVIF + WebP fallback), using Tiered Caching, and setting long TTLs with stale-while-revalidate. The bandwidth savings (30-60%) far outweigh a small hit-ratio drop.