Mark Gibbons
Published on

One Optimizer Is Better Than Two: Choosing an Image Optimization Pipeline for Your Headless Site

Authors

Images are usually the heaviest raw data on any page, so optimising image size, delivery and caching is very important. In a headless setup built on Sitecore and Next.js, you have two capable image optimizers sitting right next to each other. Sitecore can resize, crop, and compress media on its own delivery endpoint, and Next.js has its own next/image pipeline that does much the same thing closer to the visitor. Each can work on its own, but don’t use both at once.

Why two optimizers fight each other

The problem is subtle because nothing throws an error or looks particularly off. Look closer, though, and the details are soft, edges are mushy, and fine text inside a graphic loses its crispness.

What is happening is a double optimization. When a Sitecore media URL already carries its own sizing instructions, it serves an image that has been resized and recompressed once. Next.js then receives that already-shrunken, already-lossy image, treats it as the original source, and resizes and compresses again — sometimes scaling the smaller image back up to fill a layout slot. The artifacts from the first pass become the raw material for the second, and there is no recovering the detail the first pass already threw away. It is the imaging equivalent of photocopying a photocopy.

The fix is to pick one

The cleanest mental model is this: one source of truth for the original image, and exactly one optimizer that resizes it.

You have two options. Let Sitecore optimize: its media handler produces the right dimensions and format, and you hand those finished URLs straight to the browser while deliberately bypassing the framework’s optimizer so it never re-touches the bytes. The only catch here is that if you’re using Experience Edge you do have significantly different behaviour if you’re serving from Experience Edge or from Preview (the CM server itself).

Or let the framework optimize: strip the platform’s sizing instructions from the media URL so it serves the full-resolution original, and let next/image do all the resizing and compression from a pristine source.

Both are valid. What matters is that you choose, and that the layer you did not choose gets out of the way entirely.

The benefits of Next.js

I recommend letting Next.js’s Next Image do the heavy lifting here.

next/image is designed around responsive delivery. Give it the original image and a description of how big it will actually appear, and it generates a full srcset for you — a ladder of widths so a phone downloads a small variant and a large display downloads a larger one. You stop shipping one compromise size to everyone.

The power here is the sizes prop. The srcset tells the browser which widths exist; sizes tells it how much space the image will occupy at each breakpoint. Without an accurate sizes value, the browser assumes the image spans the entire viewport and reaches for the largest variant it can find — which is exactly how a small icon ends up downloading a several-thousand-pixel-wide file and being squeezed back down into a tiny box. Describe the real layout, and the browser requests a variant that matches the space, saving bytes and staying sharp. This is where most of the practical wins live.

And when you deploy to Vercel, the same code you write locally is automatically enhanced in production: optimized variants are generated on demand and cached at the edge, close to your visitors, with no separate image service to stand up.

But, it’s not a free option on Vercel.

Vercel charges you three times for this model, image optimisation requests, cache writes, and orgin data transfer. If you have plenty of head room on your Vercel billing, no worries, but this can be a significant cost issue for some.

Also a mention - complexity around cache invalidation if an author ever decides to directly update an asset with no url change, so you can’t cache assets for too long to try and improve cache hits that way without a good cache invalidation process, something I haven’t been able to figure out yet.

What it looks like in practice

First, serve the original — remove any width, height, or sizing tokens the platform appended to the media URL, since this is what prevents the double pass. Describe the layout — always provide a sizes value that reflects how wide the image really renders; treat a missing sizes as a bug, not a default. Tune quality where it counts — sit a little above the framework's default, and push flat graphics and line art higher still, since artifacts show up far more readily on solid colors and sharp edges than on photographs.

I’d recommend creating a base SitecoreImageWrapper.tsx component that all image rendering uses which implements the above logic.

Caveats on the Sitecore approach

Experience Edge supports resizing and some basic optimisation parameters, but it naively ignores your browsers Accept header so it won’t return avif or webp if your browser supports it. It also has no quality parameter, all requests are the same quality. For me those are deal breakers, but you get caught between a rock and a hard place if you are having issues with Vercel billing.

The takeaway

Two image optimizers in one pipeline is not twice as good; it is half as sharp. Decide which layer owns optimization, make the other pass the image through untouched, and you get back both quality and predictability. For most headless builds, the framework is the better owner — responsive srcset, the precision of sizes, and, on Vercel, edge-cached optimization you get simply by writing ordinary image code. One optimizer, one source of truth, one sharp result.