Cloudflare Polish vs Image Resizing: tradeoffs
Both features shrink images at Cloudflare’s edge, so teams reach for one, see a byte saving, and never evaluate the other — usually leaving the larger win on the table. This guide — part of Cloudflare Image Resizing and Polish within CDN & Edge Media Delivery — frames the choice honestly. Polish is free, automatic, and cannot resize; Image Resizing is billed, explicit, and derives any dimension you want. The right answer is often “both, on different images,” and this page shows how to draw that line by cost, plan, and image role.
The one distinction that drives everything
Polish re-encodes the bytes an image already has; Image Resizing produces new bytes at a new size. That single difference cascades into every other tradeoff:
- Polish cannot make a 3000-pixel master smaller in dimensions, only in codec. The device still decodes 3000 pixels.
- Image Resizing produces the exact pixels the slot needs, which is where the order-of-magnitude payload savings live — but it charges per unique transformation and needs either a paid plan or a Worker.
If your images are already served near their display size (a CMS that exports at render width, avatars uploaded at 96 px), Polish captures nearly the whole opportunity for free. If your images are oversized masters dropped into responsive layouts, Polish leaves most of the waste in place and Resizing is the real fix.
Decision matrix
| Dimension | Polish | Image Resizing |
|---|---|---|
| Changes pixel dimensions | No | Yes (width, height, dpr, fit) |
Auto WebP/AVIF from Accept |
Yes (WebP/AVIF toggle) | Yes (format=auto) |
| Configuration effort | One dashboard toggle | Per-image URL or a Worker |
| Plan requirement | Pro and up (dashboard toggle) | Paid tier for URL form, or any plan via Workers |
| Billing model | Included, no per-image charge | Billed per unique transformation |
Works on legacy <img src> unchanged |
Yes | No — the URL must become /cdn-cgi/image/… |
| Fixes oversized-image decode cost | No | Yes |
| Per-request crop / focal point | No | Yes (gravity, fit=cover) |
| Diagnostic header | cf-polished |
cf-resized |
| Best for | Bulk of already-sized images | Heroes, galleries, responsive srcset |
Tradeoff: Polish’s zero-effort appeal is real, but its ceiling is low on sites whose images are oversized. On a page where a 2 MB master paints into a 500-pixel column, Polish might return 1.2 MB (WebP) while Resizing returns 40 KB. The 30× difference is not a codec difference — it is a dimensions difference Polish structurally cannot touch.
Cost and plan considerations
The two features price completely differently, and conflating them causes budget surprises:
- Polish carries no per-image cost. Once enabled on an eligible plan it re-encodes everything the zone serves, forever, at no marginal charge. Its “cost” is entirely the missed opportunity of not resizing.
- Image Resizing bills per unique transformation — a distinct combination of source plus option string, counted the first time it is generated. Deliveries from cache are free; it is the generation of a new variant that meters. This makes variant discipline financial, not just architectural.
// Cost intuition: transformations = unique (source × option-string) pairs.
// This snippet estimates monthly transforms from your breakpoint plan so you
// can price Image Resizing before enabling it.
const sources = 12000; // distinct master images across the site
const breakpoints = 4; // widths you actually emit in srcset (e.g. 320/640/960/1280)
const formats = 1; // format=auto counts once per width, negotiated internally
// Each master generates (breakpoints × formats) variants the first time each
// is requested. Continuous/interpolated widths would multiply this by 100s —
// which is exactly why clamping widths to a fixed set is a COST control.
const monthlyTransforms = sources * breakpoints * formats;
console.log(`~${monthlyTransforms.toLocaleString()} transformations to warm the cache`);
// ~48,000 — compare against your plan's included transform quota.
Warning: the fastest way to blow the transformation budget is to let client JavaScript pass continuous width values (window.innerWidth) straight into the URL. Every distinct pixel value is a new billed transform and a new cache entry. Snap widths to a fixed breakpoint set — the clamp pattern in Configuring Cloudflare Image Resizing URL parameters is a cost control as much as a cache one.
When to combine both
Combining is the common production answer, drawn along image role:
- Image Resizing on the images where dimensions dominate the payload: the LCP hero, product galleries, anything wired into responsive
srcset. These justify the per-transform cost because the savings are largest and the traffic is highest. - Polish left enabled underneath for the bulk of remaining images: user-uploaded avatars served at their natural size, blog inline images from a CMS that already exports at render width, legacy templates you have not migrated to
/cdn-cgi/image/. Polish captures the codec win on all of them for free, with no markup changes.
The two do not collide. Once a URL is a /cdn-cgi/image/ transformation, Image Resizing owns its format via format=auto and Polish does not re-touch it; everything not wrapped in a resizing URL still flows through Polish. You get resized-and-negotiated heroes and codec-optimized everything-else from one configuration.
Tradeoff: running both means two diagnostic headers to reason about (cf-resized versus cf-polished) and two mental models for TTL — Resizing variants take their TTL from the resizing request’s cache settings, Polish images inherit the origin’s Cache-Control. Keep master URLs content-hashed so both models stay safe under long TTLs, per Cache-Control headers for image and video assets.
Common mistakes
1. Enabling Polish and calling image optimization “done”
Symptom: payloads dropped ~40% but LCP barely moved on image-heavy pages.
Cause: Polish fixed the codec but not the oversized dimensions driving decode and transfer.
Fix: move hero and gallery images to Image Resizing with real width breakpoints; keep Polish for the rest.
2. Reaching for Image Resizing on an already-right-sized site
Symptom: a transformation bill for images that were already served at display size. Cause: paying per transform to shrink pixels that did not need shrinking. Fix: if masters already match their slots, Polish alone captures the codec win for free — skip Resizing there.
3. Expecting Polish to crop or focal-point
Symptom: need a square avatar from a rectangular upload; Polish does nothing.
Cause: Polish never changes geometry.
Fix: cropping requires fit=cover + gravity on Image Resizing.
4. Assuming the two features fight over format
Symptom: worry that Polish will “double-encode” a resized image.
Cause: misunderstanding the boundary.
Fix: they are mutually exclusive per response — /cdn-cgi/image/ URLs are handled by Resizing only; confirm with the cf-resized versus cf-polished header.
5. Unbounded variant growth on Image Resizing
Symptom: cache hit rate low, transform count climbing every month. Cause: continuous or per-user widths creating endless unique option strings. Fix: enumerate a small breakpoint set and clamp all width inputs to it.
Related
- Cloudflare Image Resizing and Polish — the parent guide with Workers
cf.imageexamples and Polish setup - Configuring Cloudflare Image Resizing URL parameters — the full option string once you have chosen Image Resizing
- CDN & Edge Media Delivery — the cache-key and negotiation model both features implement
- Cache-Control Headers for Image and Video Assets — TTL and immutable-URL discipline for masters and derived variants