Media in a headless commerce stack

Going headless solves the coupling between your data and your front end, and it quietly creates a new problem: every front end now has to decide, on its own, what…

A schematic of three stacked backend blocks on a teal spine feeding four differently proportioned front-end silhouettes, one served through an amber stored-file symbol

The headless migration goes well. The catalogue is an API, the storefront is a separate application, a second front end appears for the app, and a third for the in-store screens. Then someone notices the app is downloading two megabyte hero images to a five inch screen, and the fix is a ticket in three repositories.

Short answer: in a headless stack the product API should return an asset identifier and a canonical delivery URL, never a set of pre-baked sizes. Each front end appends the parameters it needs at request time. That keeps the shape decision in the front end that knows the viewport, and the asset decision in the system that owns the asset, which is the only split that stays stable as you add channels.

What headless changes about media

Headless architecture removes the assumption that there is one front end. That assumption was carrying more weight than anyone realised, because it was also the assumption that let you decide image sizes once, centrally, at build time.

With one front end, a fixed set of renditions is workable. You know the breakpoints, you generate five sizes, you are done. With four front ends on different frameworks, different devices and different release cycles, a fixed set is wrong for at least three of them within a quarter, and the process for adding a size is a cross-team negotiation.

The second thing headless changes is who is holding the responsibility. In a monolith the platform knew both the layout and the asset. Split them and neither side knows both, so the contract between them has to carry enough information for the front end to decide.

The API contract that works

Return three things per asset, per product:

A stable identifier. Opaque, permanent, meaningful only to the asset system. This is what the product record stores, for the reasons in wiring a DAM to a PIM.

A canonical delivery URL. The base from which any rendition can be constructed by appending parameters. Not a rendition itself.

Enough metadata to render responsibly. Intrinsic width and height, so the front end can reserve space and avoid layout shift. The default description, so alt text exists without another call. Focal point if you have one.

That is the whole contract. Notably absent: a list of sizes. The moment the API returns thumbnail, medium and large, you have encoded three front ends’ assumptions into a shared interface, and the fourth front end will either misuse one of them or ask for a fourth name.

Let the front end ask for what it needs

With a URL-parameter delivery layer, each surface constructs what it wants at request time. A Cloudinary URL is the clearest example of this contract in practice, because the parameters sit in the path and can be generated by the consumer without a round trip.

For a responsive web front end the useful set is narrow. Ask for automatic format so AVIF and WebP go to browsers that support them and JPEG to those that do not, which is what the format support matrix otherwise makes you handle by hand. Ask for automatic quality so compression is chosen per image. Then emit a srcset with the widths your layout actually uses, following the responsive images guidance, and let the browser choose.

For a native app, ask for the device pixel ratio you have and nothing else. For a screen in a store, ask for the fixed size of the screen. Each surface answers a question only it can answer, and none of them need a deployment in another repository to do it.

The performance argument for this is not subtle. Images are usually the largest contentful paint element on a product page, and serving a correctly sized modern format is most of the fix. Delivering WebP or better instead of an oversized JPEG typically moves that metric more than any front-end optimisation you will do this quarter.

One source square linked by teal parameter lines to eight differently proportioned outputs, with the same eight redrawn in amber inside a hatched storage container

Caching, which is the part that surprises people

A derived-rendition model produces a very large number of distinct URLs, and that is fine, because each one is immutable.

The rules that keep it fast:

  • Make the URL the cache key and make it deterministic. The same parameters must produce the same URL every time, in every front end. A shared helper is worth more than a documented convention.
  • Cache derived output aggressively and for a long time. An immutable URL can be cached effectively forever, following the standard HTTP caching guidance. The first request pays the transformation cost and nothing else does.
  • Version by identifier, not by cache busting. When an asset is replaced, the identifier or version component changes and the old URL simply stops being referenced. Purging a CDN by pattern is a thing you should never need to do routinely.

The consumption implication matters for the bill as well. Repeated delivery of an already-generated derivative does not re-run the transformation, so the cost of the long tail of URLs is storage and bandwidth rather than compute.

Do not put media logic in the front end

The failure mode of a headless stack is that each front end grows its own image utility, and after a year the four utilities disagree.

Keep a single shared definition of the shapes your business uses. Named transformations are the mechanism if you are on a platform that supports them: define product_card, product_hero, swatch once, and let every front end reference the name. When the design changes, the definition changes and every surface follows, without a coordinated release.

If your delivery layer has no such feature, achieve the same thing with a small shared package that constructs URLs. The important property is that the parameters exist in exactly one place.

Nine flat edge nodes ringing a teal-edged origin cube, three of them holding an amber cube that does not match the origin silhouette

When is a headless media layer overkill?

If you have one front end and no plans for a second, most of this page is theory. A monolithic storefront with a built-in media library, a sensible set of breakpoints and a CDN in front of it is a perfectly good architecture and will outperform a badly assembled headless stack.

The threshold is the second surface. The moment a native app, a marketplace, a partner site or a physical screen consumes the same catalogue, the assumptions that made a fixed rendition set workable stop holding, and every month you delay makes the change larger.

That is also the reason to put the delivery layer in early rather than after the second front end arrives. Adopting it on day one costs a URL helper and an account. Adopting it in month eighteen means finding every hard-coded image path in three codebases and every stored derivative in the catalogue. The developer-side version of that argument is in DAM for developers, and the properties an asset needs in order to be addressable this way are in what a DAM actually does. If you are also choosing a product API, what to demand from a PIM API before you buy covers the other half of the contract.