Image CDNs and on-the-fly transformation
Origin-based versus URL-based transformation, resize and quality parameters, format negotiation, caching generated variants, and the pricing shapes that catch people out.
Two ways to transform
| Approach | How it works | Strength | Weakness |
|---|---|---|---|
| Origin transformation | You generate variants at upload or build time | Full control, cheap at request time | Every new size requires a backfill |
| URL-based transformation | The CDN generates the variant on demand | Any size, immediately | First request is slow; each variant is billed |
| Hybrid | Common sizes pre-generated, the rest on demand | Fast for the common case | Two code paths to keep consistent |
<!-- URL-based transformation: the variant is described in the URL -->
<img src="https://cdn.example.com/img/hero.jpg?w=800&q=75&fm=webp"
srcset="https://cdn.example.com/img/hero.jpg?w=400&q=75&fm=webp 400w,
https://cdn.example.com/img/hero.jpg?w=800&q=75&fm=webp 800w,
https://cdn.example.com/img/hero.jpg?w=1600&q=75&fm=webp 1600w"
sizes="(max-width: 600px) 100vw, 60vw"
width="1600" height="900" alt="Order dashboard" />- Every distinct parameter combination is a separate cached object. Use a small, fixed set of widths rather than a free-form value.
- Always set a width and height on the element. A transformed image that changes size on load causes layout shift.
- Be careful with automatic format negotiation in
srcset: a CDN that serves a different format per browser also needsVary: Accept, and many do not send it.
Caching generated variants
# a transformed variant is deterministic, so it can be cached hard
Cache-Control: public, max-age=31536000, immutable
# the transformation response must vary on what actually changes it
Vary: Accept
# and a limit on the number of generated variants avoids runaway cost
# e.g. allow w in {200,400,800,1200,1600}, q fixed at 75| Pitfall | What happens | Prevention |
|---|---|---|
| Unbounded width values | Thousands of variants, all billed and stored | Whitelist allowed parameters |
| Slow first request | The first user waits for the transform | Pre-warm the common variants after deploy |
| Transform billed per request | A cache bypass makes every view a transform | Verify the cache headers on the variant |
| Signed URLs on the image path | The cache key includes the signature | Sign the page, not each image, or use a stable signature |
| Format negotiation without Vary | A WebP served to a client expecting JPEG | Set the header, or use picture markup |
| Quality set per request | Extra variants for an invisible difference | Fix quality in the transform configuration |
# is a variant cached after the first request?
curl -sI "https://cdn.example.com/img/hero.jpg?w=800&q=75" | grep -iE "age|cache-control|content-type"
curl -sI "https://cdn.example.com/img/hero.jpg?w=800&q=75" | grep -iE "age|cache-control|content-type"
# does the CDN vary by Accept?
curl -sI -H "Accept: image/webp" "https://cdn.example.com/img/hero.jpg?w=800" \
| grep -iE "content-type|vary"Cost and quality
- Fix the set of widths your layout actually uses. Three or four is usually enough.
- Pre-generate or pre-warm the most common variants right after a deploy, so no user pays the first-request cost.
- Set quality once at the CDN level and do not expose it in the URL. The difference between 75 and 80 is not visible and doubles the variants.
- Watch the ratio of transformations to cache hits. If transformations grow with traffic, caching is broken.
- Measure real quality with a screenshot comparison at your chosen settings rather than assuming lower is fine.
| Setting | Common value | Notes |
|---|---|---|
| Width | 400, 800, 1200, 1600 | Match the breakpoints in the layout |
| Quality | 70-80 for JPEG and WebP | Below 70 shows artefacts on gradients |
| Format | auto or explicit | Explicit is easier to reason about |
| Fit | cover for cards | Crops instead of distorting |
| Metadata | Stripped | Both smaller and better for privacy |
| Upscaling | Disabled | Never generate a variant larger than the source |
⚠️
An image CDN charges for the transform and then for the egress of every variant. Allowing arbitrary width values in a query string means any visitor can generate and consume unlimited billable transformations by requesting unusual sizes - whitelist the parameters or the bill becomes a denial-of-service vector.
FAQ
Should I use an image CDN at all?
If you serve images at more than one size, yes. Doing it yourself means maintaining a pipeline, storage for every variant and a resizing service, and the CDN usually does it better and cheaper.
What about art direction?
URL parameters handle resizing and cropping. Changing the composition per breakpoint needs different source images chosen in markup with ``, which is a design decision, not a transform.
Related
Static asset optimisation: compression and modern images Video and large file delivery
Last refreshed 2026-09-18.