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.

Choosing between Polish and Image Resizing A decision flow: first ask whether the served image is already close to its display size. If yes, Polish alone captures the win. If no, ask whether you can change markup or add a Worker. If yes, use Image Resizing for correct dimensions. If markup is fixed but bytes still matter, use Polish as the free fallback. A final node notes combining both across different images. Image already near its display size? Yes Polish only free, zero config No Can edit markup or add a Worker? Yes Image Resizing correct pixels, billed No Polish as free fallback codec win without dimension fix combine: Resizing on heroes, Polish on the rest

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.