
8 Signs Your Website Needs an Image CDN
How to tell whether an image CDN is worth it - variant explosion, uncontrolled uploads, international latency, format lag, origin load - plus an honest look at when a build step is still the better answer.
An image CDN is not automatically the right answer. Plenty of sites are better served by a build step and a plain static host, and paying monthly for something a sharp script does at build time is waste.
But there are eight situations where a build step stops working, and once you are in one of them the CDN pays for itself quickly. This guide describes each sign, explains why the build-time approach breaks, and ends with an honest list of cases where you should not bother.
What an Image CDN Actually Does
The name undersells it. A plain CDN caches whatever bytes the origin gave it. An image CDN caches, but it also transforms on request:
| Capability | Plain CDN | Image CDN |
|---|---|---|
| Edge caching | Yes | Yes |
| Resize on request | No | Yes |
Format negotiation from Accept |
No | Yes |
| Crop, rotate, watermark by URL | No | Yes |
| Convert HEIC, TIFF, RAW on ingest | No | Usually |
| Automatic quality tuning | No | Usually |
That single difference, transforming per request, is what makes the eight signs below solvable.
1. You Generate More Files Than You Have Images
Count what your build produces. Four widths, three formats, and a 2× retina variant is twelve to sixteen files per source image.
| Source images | Widths | Formats | Files generated | Build time impact |
|---|---|---|---|---|
| 50 | 4 | 3 | 600 | Seconds |
| 500 | 4 | 3 | 6,000 | Minutes |
| 5,000 | 4 | 3 | 60,000 | Tens of minutes |
| 50,000 | 4 | 3 | 600,000 | Impractical |
Why It Breaks
The cost is not only build time. It is storage, deploy size, cache invalidation, and the fact that most of those variants are never requested. A 2400 px AVIF exists for every image, but only a fraction of visitors ever ask for one.
What Changes With a CDN
You store one original. Variants are produced on first request and cached at the edge afterwards.
https://demo.sirv.com/lamp.jpg?w=400
https://demo.sirv.com/lamp.jpg?w=800
https://demo.sirv.com/lamp.jpg?w=1600
Three URLs, one stored file, and the widths nobody requests are never generated.
The Threshold
Below roughly 500 source images, a build step is fine. Above a few thousand, variant explosion becomes the dominant cost of your pipeline.
2. Adding a Breakpoint Means Rebuilding Everything
A designer changes the product grid from three columns to four. The displayed image width changes from 380 px to 290 px. Your srcset ladder no longer matches.
With a Build Step
- Update the width list in the build config
- Regenerate every derivative
- Redeploy
- Purge the CDN cache
- Discover a template that hardcoded the old widths
With an Image CDN
Change the number in the URL.
<img srcset="https://demo.sirv.com/lamp.jpg?w=290 290w,
https://demo.sirv.com/lamp.jpg?w=580 580w,
https://demo.sirv.com/lamp.jpg?w=870 870w"
sizes="(max-width: 768px) 50vw, 290px"
src="https://demo.sirv.com/lamp.jpg?w=290"
width="290" height="290" alt="…">
The first request for each new width takes a little longer while the CDN generates it. Every request after that is a cache hit.
The Signal to Watch For
If your team avoids design changes because of the image rebuild cost, the pipeline is dictating the design. That is the wrong way round.
3. Users Upload Images You Do Not Control
The moment a site accepts uploads, every assumption breaks. You receive:
- 12 megapixel phone photos, 8 MB each
- HEIC files that only Safari can decode, sometimes with an empty MIME type
- Portrait photos rotated sideways because the EXIF orientation was ignored
- Screenshots saved as BMP
- CMYK TIFFs from a designer
- Images with GPS coordinates embedded
Why a Build Step Cannot Help
A build step runs at deploy time. Uploads arrive afterwards. You need processing at ingest or at request time, which means either building an upload pipeline yourself or letting the CDN handle it.
The Self-Built Version
import sharp from 'sharp';
import convert from 'heic-convert';
async function normalizeUpload(buffer, mimetype, filename) {
let working = buffer;
// iOS sometimes reports an empty MIME type, so check the extension too
const isHeic = /\.hei[cf]$/i.test(filename) ||
['image/heic', 'image/heif'].includes(mimetype);
if (isHeic) {
working = await convert({ buffer, format: 'JPEG', quality: 0.92 });
}
return sharp(working)
.rotate() // apply EXIF orientation, drop the tag
.resize({ width: 2400, withoutEnlargement: true })
.toColorspace('srgb')
.jpeg({ quality: 88, progressive: true, mozjpeg: true })
.toBuffer(); // metadata stripped by default
}
That is a reasonable amount of code to write, test, and maintain, plus a queue for large files, plus retry handling, plus a storage layer. An image CDN accepts the original and does all of it on delivery. Sirv accepts HEIC uploads directly and serves WebP or AVIF to browsers.
The user-generated content guide covers the full ingest problem including moderation and abuse.
4. Your Traffic Is International and Your Origin Is Not
Latency is governed by distance. A request from Sydney to a server in Virginia takes roughly 200 ms for the round trip before any bytes move, and TLS negotiation multiplies that.
| Visitor location | Origin in Virginia | Edge in region |
|---|---|---|
| Virginia | 15–30 ms | 15–30 ms |
| London | 80–110 ms | 20–40 ms |
| Sydney | 200–260 ms | 20–50 ms |
| São Paulo | 130–180 ms | 25–55 ms |
| Mumbai | 230–300 ms | 25–60 ms |
Multiply by the number of images on the page and the connection setup for a new host, and distant visitors experience a fundamentally slower site.
How to Check
Google Analytics or your server logs will show the country split. Then compare Core Web Vitals by country in the Chrome UX Report. If LCP passes in your home market and fails elsewhere, distance is a large part of it.
The Nuance
A plain CDN solves latency. You do not need an image CDN for this sign alone. It matters here because if you are already putting a CDN in front of images, choosing one that also transforms costs little extra and removes the build step at the same time.
5. You Still Ship Only JPEG and PNG
You know AVIF and WebP would cut 25% to 50%. It has been on the backlog for a year. The blocker is not knowledge, it is that <picture> markup has to be threaded through every template, and the build has to generate every format.
What Manual Format Support Costs
<picture>
<source
srcset="hero-600.avif 600w, hero-900.avif 900w, hero-1200.avif 1200w"
sizes="100vw" type="image/avif">
<source
srcset="hero-600.webp 600w, hero-900.webp 900w, hero-1200.webp 1200w"
sizes="100vw" type="image/webp">
<img
src="hero-1200.jpg"
srcset="hero-600.jpg 600w, hero-900.jpg 900w, hero-1200.jpg 1200w"
sizes="100vw" width="1200" height="630" alt="…">
</picture>
That is nine files and eighteen lines for one image. Multiply across a template library and it becomes something nobody volunteers to maintain.
What the CDN Version Costs
<img src="https://demo.sirv.com/hero.jpg?w=1200"
srcset="https://demo.sirv.com/hero.jpg?w=600 600w,
https://demo.sirv.com/hero.jpg?w=900 900w,
https://demo.sirv.com/hero.jpg?w=1200 1200w"
sizes="100vw" width="1200" height="630" alt="…">
One file stored, one element. The CDN reads the Accept header and returns AVIF to browsers that accept it, WebP to those that do not, and JPEG to anything older.
The Real Signal
If modern formats have been “next quarter” for more than two quarters, the obstacle is structural. Removing the markup burden is usually what unblocks it.
6. Image Traffic Is Consuming Your Application Servers
Check what share of requests to your origin are images. On a typical content or commerce site it is 60% to 80% of requests and a larger share of bytes.
Why That Is Expensive
Every image request that reaches your application server occupies a worker, a connection, and bandwidth, to do something entirely mechanical. Application servers are the most expensive way to serve a static file.
# What share of your origin requests are images?
awk '{print $7}' access.log \
| grep -oiE '\.(jpe?g|png|gif|webp|avif|svg)(\?|$)' \
| wc -l
Compare that to the total line count. If images are the majority and your cache headers are weak, you are paying application-tier prices for static delivery.
Cache Headers First
Before adding anything, check whether your images are cacheable at all:
curl -sI https://example.com/images/lamp.jpg | grep -i 'cache-control\|etag\|expires'
If there is no Cache-Control, fix that first. Versioned filenames plus a long max-age solves a large part of this problem for free:
Cache-Control: public, max-age=31536000, immutable
An image CDN then takes near enough all remaining image traffic off the origin, because it serves from cache and only fetches the original once.
7. You Are Building Zoom, Galleries, or 360 Spin Yourself
A product page needs a gallery with thumbnails, pinch-zoom on mobile, hover-zoom on desktop, fullscreen, keyboard navigation, and a 360 spin for some categories. Building that properly is weeks of work, and getting it accessible and fast is the hard part.
What Makes It Hard
| Requirement | Why it is difficult |
|---|---|
| Zoom without a huge initial download | Needs tiled or region-based loading |
| Pinch and pan on touch | Gesture handling conflicts with page scroll |
| Fullscreen | Fullscreen API differs across browsers, especially iOS |
| Keyboard navigation | Focus management inside a modal |
| 360 spin | Frame preloading, drag inertia, direction handling |
| No layout shift | Reserved aspect ratios for every slot |
| LCP safety | First slide must be in the HTML, not injected |
The Alternative
The Sirv Media Viewer handles stills, zoom, 360 spin, video, and 3D in one component, and it requests only the zoomed region at full resolution instead of downloading a huge image up front.
<div class="Sirv" data-options="thumbnails.enable:true; zoom.mode:hover">
<img data-src="https://demo.sirv.com/lamp.jpg" alt="Walnut desk lamp, front view">
<img data-src="https://demo.sirv.com/lamp-detail.jpg" alt="Brushed brass shade detail">
<div data-src="https://demo.sirv.com/lamp-spin.spin"></div>
</div>
The 360 product viewer guide covers capture and configuration.
8. Non-Technical People Upload Images and Nothing Stops Them
Marketing uploads a 6 MB PNG hero through the CMS. A merchandiser uploads a 5000 px product shot. Nobody notices until a Lighthouse score drops two months later.
Why This Is the Most Common Regression
Every other image problem is fixed once. This one recurs forever, because the people uploading are not the people who measure performance, and the CMS accepts whatever it is given.
The Two Defences
A CI image budget catches images committed to the repository:
// scripts/check-image-budget.mjs
import { readdir, stat } from 'node:fs/promises';
import { join, extname } from 'node:path';
const LIMITS = { '.jpg': 250_000, '.png': 150_000, '.webp': 200_000, '.avif': 150_000 };
const walk = async (dir) => {
const entries = await readdir(dir, { withFileTypes: true });
const nested = await Promise.all(entries.map(async (e) => {
const p = join(dir, e.name);
return e.isDirectory() ? walk(p) : [p];
}));
return nested.flat();
};
const bad = [];
for (const file of await walk('public/images')) {
const limit = LIMITS[extname(file).toLowerCase()];
if (!limit) continue;
const { size } = await stat(file);
if (size > limit) bad.push(`${file}: ${Math.round(size / 1024)} KB`);
}
if (bad.length) { console.error('Over budget:\n' + bad.join('\n')); process.exit(1); }
An image CDN catches everything else, because it does not matter what was uploaded. A 6 MB PNG stored at the origin is still delivered as a 90 KB AVIF at the width the page requested. The upload stays wrong; the delivery is right.
That second point is the strongest argument for a CDN in an organisation where you do not control who uploads.
When You Do Not Need One
Being honest about this matters more than the eight signs above.
| Situation | Why a build step is better |
|---|---|
| Under ~200 images, all controlled by developers | Sharp at build time is free and fast |
| Static site, one region of traffic | A plain CDN or even a good host is enough |
| Images change rarely | Nothing to regenerate, so no ongoing cost |
| Strict data residency rules | A third-party CDN may be disallowed |
| Fully offline or intranet deployment | No edge network to use |
| Already on a platform with built-in optimization | Shopify, Vercel, Netlify and Cloudflare Images all transform already |
That last row catches many people. If you are on a platform whose image pipeline already resizes and negotiates formats, adding a separate image CDN duplicates work. Check what you already have before paying for more.
The Cost Question
Image CDN pricing is usually based on some mix of stored bytes, delivered bytes, and transformations. Two things drive the bill more than anything else:
- Cache hit ratio. A well-configured setup transforms each variant once and serves it from cache thereafter. A setup that generates hundreds of near-identical widths transforms constantly.
- Number of distinct URLs. Standardise on a fixed width ladder.
?w=800and?w=801are two cache entries and two transformations for no visible benefit.
Fix those two and the bill is usually smaller than the engineering time the build pipeline was consuming. The CDN comparison compares the pricing models directly.
How to Decide
Score yourself. Each sign that applies is one point.
| Sign | Applies? |
|---|---|
| 1. More generated files than source images | |
| 2. Breakpoint changes require a rebuild | |
| 3. Users upload images you do not control | |
| 4. International traffic, single-region origin | |
| 5. Still shipping only JPEG and PNG | |
| 6. Images dominate origin requests | |
| 7. Building zoom, galleries, or spin yourself | |
| 8. Non-developers upload images freely |
0–1 points: Stay with a build step. Sharp and a plain CDN cover you.
2–3 points: Borderline. Fix cache headers and add srcset first, then reassess.
4 or more: A CDN will pay for itself, most likely in engineering time before bandwidth.
Signs 3, 7, and 8 are worth more than one point each in practice, because each represents a class of work that never finishes.
Migrating Without a Rewrite
You do not need to move everything at once.
- Point the CDN at your existing origin. Most image CDNs support origin pull, so they fetch from your current storage on first request. No migration of files required.
- Change one template. Product images, or the hero, whichever is heaviest.
- Compare. Measure LCP and total image bytes on that template before and after.
- Roll forward template by template. Keep the old paths working throughout.
- Move storage later, or never. Origin pull can remain your permanent arrangement.
<!-- Before -->
<img src="/images/products/lamp.jpg" width="800" height="800" alt="…">
<!-- After: same file, same origin, transformed on delivery -->
<img src="https://youraccount.sirv.com/images/products/lamp.jpg?w=800"
srcset="https://youraccount.sirv.com/images/products/lamp.jpg?w=400 400w,
https://youraccount.sirv.com/images/products/lamp.jpg?w=800 800w,
https://youraccount.sirv.com/images/products/lamp.jpg?w=1200 1200w"
sizes="(max-width: 768px) 100vw, 400px"
width="800" height="800" alt="…">
Create a Sirv account if you want to test this against one template before committing. The image hosting guide covers origin pull and storage arrangements in more detail.
Summary
The Eight Signs
| # | Sign | What breaks | What a CDN changes |
|---|---|---|---|
| 1 | Variant explosion | Build time, storage | One original, transform on request |
| 2 | Rebuilds for design changes | Design velocity | Widths become URL parameters |
| 3 | Uncontrolled uploads | Everything | Normalisation on delivery |
| 4 | International latency | Distant visitors | Edge delivery |
| 5 | No modern formats | 25–50% extra bytes | Accept header negotiation |
| 6 | Origin serving images | Server cost | Cached at the edge |
| 7 | Building viewers yourself | Weeks of work | Ready-made viewer |
| 8 | Uncontrolled uploaders | Permanent regression | Delivery is right regardless |
Checklist Before You Buy
- ✅ Cache headers on your current images are correct
- ✅
srcsetandsizesare in place, so you know the widths you need - ✅ You checked whether your platform already transforms images
- ✅ You counted how many of the eight signs apply
- ✅ You have a fixed width ladder, not arbitrary widths
- ✅ You tested on one template with origin pull before migrating storage
- ✅ You measured LCP and image bytes before and after
The strongest case for an image CDN is rarely bandwidth. It is that signs 3, 7, and 8 describe work with no end, and moving that work to delivery is what stops it recurring.