Sign-ups are closed. Menestrel now powers the sites built by Agora Studio, a web agency based in France.

Images in Astro: what a CMS should do for you

Your client uploads a four-megabyte photo from their phone, sideways. Everything that has to happen next, which parts Astro handles, and which parts a CMS should stop making you build.

Here is a scenario that happens on every client site, several times a year.

Your client photographs their new shopfront on a Tuesday afternoon. The file is 4.2 MB, 4032 by 3024 pixels, JPEG, with EXIF orientation set to 6 because they held the phone sideways. They upload it into whatever interface you gave them, and expect it to appear on their homepage looking correct.

Between that upload and a fast, correct page, there are roughly eight things that must happen. Knowing which ones are your problem is the difference between a site that stays fast and one that quietly degrades over two years.

The eight things

1. Accept the file at all. Modern iPhones default to HEIC. Browsers do not display HEIC. If your pipeline does not convert it, the client uploads a photo, sees a broken image, and concludes the tool is broken.

2. Fix the orientation. EXIF orientation is metadata saying “this image is rotated”. Some renderers honour it, some do not, and once you resize without accounting for it you get a sideways photo permanently. Strip the metadata and apply the rotation for real.

3. Strip the rest of the metadata. That photo contains GPS coordinates. Publishing a client’s home address because they photographed something in their garden is a privacy incident with your name on it.

4. Resize. Nobody needs 4032 pixels. You want a set of widths covering the layouts the image appears in, typically something like 400, 800, 1200 and 1600.

5. Convert formats. AVIF where supported, WebP as the fallback, and the original format for the rest. AVIF routinely halves WebP’s size on photographs.

6. Serve from a CDN, with cache headers long enough to matter. An image that is regenerated per request is a bill and a latency problem.

7. Carry dimensions through to the markup. Width and height on the tag, or an aspect ratio in CSS, so the page does not jump while images load. This is most of what Cumulative Layout Shift measures, and it is the easiest Core Web Vital to fail.

8. Have alt text. Which means someone has to write it, which means the interface has to ask for it at the moment of upload, because nobody goes back later.

What Astro handles

Astro’s <Image /> component and its image service are genuinely good, and they cover items 4, 5 and 7 for local images: files that exist in your project at build time. Import an image, pass it to <Image />, get responsive output with correct dimensions and modern formats.

The catch is the phrase “at build time, in your project”. Astro optimises what it can see. A URL from a CMS is, from Astro’s point of view, just a string. It cannot resize what it has not got.

There is remotePatterns for allowing remote optimisation, which helps, but it means your build now downloads and processes every client photo on every build. On a site with two hundred images that turns a fast build into a slow one, and it happens on every deploy, forever.

Where a CMS should take over

The division that works: the CMS owns the pipeline, Astro owns the markup.

Concretely, that means by the time your build sees an image, the following is already true: it has been converted, stripped, rotated, resized into a set of derivatives, and put behind a CDN. What arrives in your content is a URL plus the metadata you need, dimensions and alt text included.

Your build does no image work at all. Deploys stay fast regardless of how many photos the client uploaded, because processing happened once, at upload, rather than once per build.

This is what Menestrel does. The upload is a presigned PUT straight to object storage, so a large file never passes through the API. Processing happens on a worker: format detection, EXIF handling, metadata stripping, resizing into a fixed set of widths, AVIF and WebP derivatives. Delivery goes through signed imgproxy URLs behind a European CDN, so a derivative that does not exist yet is generated once, cached, and never computed again.

The component side then stays trivial:

---
import { CmsImage } from '@menestrel/astro/components';
const { entry } = Astro.props;
---
<CmsImage image={entry.data.photo} sizes="(max-width: 720px) 100vw, 720px" />

Dimensions come from the content, so no layout shift. The srcset is generated from the derivative widths. Alt text comes from the media library, where it was collected at upload.

The alt text problem, which is a design problem

Every CMS has an alt text field. Most sites have empty alt text everywhere.

The reason is where the field sits. If alt text is an optional box on a media library screen the client visits once, it stays empty. If it is asked for at the moment of upload, in the flow, most clients fill it in, because they have just chosen the photo and know what is in it.

It is a small design decision with a large effect on whether a site is usable by someone with a screen reader, and it is worth checking in any CMS you evaluate: where, exactly, does it ask for alt text?

The related decision is the focal point. A photo cropped to a wide banner and to a square thumbnail cannot be cropped from the centre in both cases without cutting someone’s head off. Letting the editor set a focal point once, and having every derivative respect it, removes an entire category of “the photo looks wrong on mobile” messages.

Where the quota lives

Media is where a client site’s storage actually goes. Text is negligible; photos are everything.

Two things worth checking in any CMS: whether deleting an image frees the quota, and whether an image still referenced by a published version can be deleted at all.

The second one is subtle and matters. If a client deletes a photo that a published page still uses, and the system lets them, the live site breaks. If the system silently keeps the file, the quota is a lie. The correct behaviour is to refuse the deletion and say why, which is what we do: a media file referenced by a retained snapshot cannot be purged, and the interface says which snapshot and when it expires.

Why processing at upload beats processing at build

This is the architectural point of the whole article, and it is worth stating on its own.

If images are processed during your build, the work is repeated on every deploy. Change a paragraph of text on a site with two hundred photos, and your build downloads, decodes, resizes and re-encodes two hundred images to produce a page where one sentence changed. Caching helps until the cache is cold, which is exactly when you least want it: on a fresh CI runner, on the deploy you are watching because a client is waiting.

If images are processed at upload, the work happens once per image, ever. Your build receives URLs and metadata. A deploy is as fast on a site with two thousand photos as on one with none.

The knock-on effect matters more than the build time. When deploys are slow, publishing feels slow, and the whole promise of “your client clicks Publish and the site updates” degrades into “your client clicks Publish and waits four minutes”. We aim for one to three minutes from click to live page, and that budget is only achievable because the build does no image work.

The Core Web Vitals angle, briefly

Two of the three Core Web Vitals are decided largely by images.

Largest Contentful Paint is usually the hero image. What helps: correct format, the right size for the viewport, and not lazy-loading it. That last one is the most common self-inflicted wound, because lazy loading is applied by default to everything and the hero is the one image that must load eagerly. If your CMS component defaults everything to lazy, your hero is slow and no amount of optimisation elsewhere compensates.

Cumulative Layout Shift is images without dimensions. If the browser does not know how tall an image will be, it reserves nothing, then reflows the page when the bytes arrive. This is why dimensions must travel with the content rather than being something you hardcode per template.

Interaction to Next Paint is mostly JavaScript, which on an Astro brochure site is close to none. This one you get for free by having chosen a static site generator.

So the practical rule: let the component set width and height from the content, and give yourself a way to mark the hero as eager. Ours takes loading and fetchpriority for exactly that.

What we deliberately do not do

No image editing beyond cropping. No filters, no brightness, no text overlay. If a client needs that, they need a photo editor, and there are good ones.

A fixed set of derivative widths. You cannot request an arbitrary size. This keeps the cache useful and the storage bounded, and it means a design needing an unusual width has to use the nearest larger one.

Formats we accept are limited: JPEG, PNG, WebP, AVIF, GIF and PDF. No SVG, deliberately, because an SVG is a document that can carry script, and accepting arbitrary SVG uploads from clients is a cross-site scripting vector wearing a picture costume.

25 MB per file, 4096 pixels maximum. Enough for any brochure site photograph, small enough to keep processing predictable.

The checklist

If you are evaluating a CMS for a site with real photographs on it, ask:

  1. What happens when I upload a HEIC from an iPhone?
  2. Is EXIF stripped, and is orientation applied?
  3. Which derivative widths and formats come out, and are they generated once or per build?
  4. Do I get dimensions in my content, so I can avoid layout shift?
  5. Where does it ask for alt text?
  6. Can I set a focal point, and do crops respect it?
  7. What happens if a client deletes an image a live page uses?

Most of those questions have nothing to do with the CMS’s marketing page, and all of them will affect your client’s site for years. The comparisons cover how the main tools handle the editing side more broadly.

Back to the blog