Feature Image Workflow: Meta, Sizes and a Simple Call
How a hero image goes from one source file to responsive AVIF and WebP variants, with front matter meta, size tokens and a one-line component call.
A feature (hero) image has three jobs: look right at every screen width, load fast, and give search and social previews a proper picture. The workflow keeps each job in one place, so you drop in one file and set a few front matter fields.
1. Add the source file
Save one image in src/site/assets/images/. Images for avatars, profiles and featured art can also live in the avatar, profile and featured sub-folders; all folders are set in src/site/config/images.mjs.
- Aim for 1200 x 630 px, PNG or WebP, under 200 KB, with no text baked into the picture (see
docs/seo-image-checklist.md). - Use a descriptive file name. It becomes the image key and shows up in image search.
2. Build-time variants and meta
core/scripts/generate/images.mjs runs in the prebuild step of pnpm run dev and pnpm run build. It uses Sharp to:
- write AVIF and WebP (plus the original format) files to
public/images/YYYY/MM/, named<name>-w<width>-a<aspect>.<ext>, - pick the nearest aspect label (
16x9,1x1,4x3,2x1and so on), - skip any width the source is too small for, so nothing is upscaled,
- record the result in
src/data/manifest/images.json: path, variants, formats, aspect and a content hash.
That manifest is the image “meta”. Layouts read it through getImageMeta() to get exact dimensions without opening the file at render time.
3. Size definitions
Sizes are tokens in src/site/config/images.mjs, not numbers scattered through components.
| Setting | Default | Meaning |
|---|---|---|
imageVariants |
380, 640, 1280 | Widths generated for every image |
variants.mobile / tablet / desktop / full |
380 / 640 / 640 / 1280 | Which width each screen class asks for |
variants.featured |
960 | Width used for the featured slot |
variants.thumbnail / avatar |
120 / 60 | Small card and author sizes |
breakpoints |
460 / 1079 / 1080 / 1440 | Mobile max, tablet max, desktop and full minimums |
imageFormats, legacyImageFormats |
avif, webp | Formats for R2 sync and for local images |
compressionLevel |
80 | Quality for generated files |
The site defaults for pages without their own image are image, featuredImage and thumbnailImage, each with Alt and Title companions.
4. Front matter
heroImage: "my-feature-image.png"
heroImageAlt: "What the picture shows, in one sentence"
heroImageTitle: "Optional title"
heroImageCaption: "Optional caption"
Entries without heroImage fall back to the site default, which is why every page should set its own: a shared image makes every social preview look identical.
5. The simple call
The post layout already renders the hero for you, with loading="eager" so the largest image is preloaded and does not lazy-load. In your own markup the call is one line:
---
import ResponsiveImage from 'core/components/ResponsiveImage.astro';
---
<ResponsiveImage src="my-feature-image.png" alt="Describe the image" sizes="(max-width: 768px) 100vw, 960px" />
ResponsiveImage looks up the meta, builds srcset and sizes, sets an a-<aspect> class so the box keeps its shape (no layout shift), and registers the image for JSON-LD. Useful props: variant (auto, thumbnail, desktop, featured), loading (lazy or eager), width, objectFit and addSchema. For a plain file with no variants use SimpleImage, which takes src, alt, width, height, loading and fetchPriority.
Eager images are collected in a preload registry and printed once as <link rel="preload" as="image"> by ImagePreload.astro, so you do not add preload tags by hand.
6. Larger media: R2
Public files can also be served from Cloudflare R2. Use a media: key as src (or the mediaKey prop) and ResponsiveImage resolves the stored variants and alt text through resolveMedia. See Images: Local Variants, Public Files and R2.
Checklist
- One source file in
src/site/assets/images/. - Run
pnpm run devorpnpm run buildso the variants andimages.jsonare regenerated. - Set
heroImage,heroImageAltandheroImageTitlein front matter. - Check the page source: one
preloadfor the hero, asrcset, width and height present.