Two Astro releases changed the right answer here. Responsive images left experimental in Astro 5.10, so the layout prop and the priority shortcut are now stable defaults rather than flags. Then Astro 6.1 (March 26, 2026) added codec-specific Sharp config, so you tune AVIF and WebP encoding once instead of per image. Most tutorials still teach the pre-5.10 pattern of hand-written widths and manual loading="eager". This is the setup that actually holds up on a 1,000-article content site as of June 2026.
TL;DR
- Use
<Picture>withformats={['avif', 'webp']}and let the<img>fallback cover everything else. AVIF is 20-30% smaller than WebP at the same visual quality, with roughly 95%+ browser support in 2026. - Add
layout="constrained"so Astro generatessrcsetandsizesfor you. Stop hand-writingwidthsfor body images. - Set
priorityon the single LCP image. It expands toloading="eager",decoding="sync", andfetchpriority="high"in one prop. Everything else lazy-loads automatically. - Configure AVIF and WebP
effortonce inastro.config.mjsunderimage.service.config(new in 6.1) instead of fighting build time per image.
What changed in Astro 5.10 and 6.1
The image pipeline still runs at build time on Sharp, generating resized variants, transcoding formats, and rewriting your HTML into a <picture> with srcset. What changed is how much you configure by hand.
| Capability | Pre-5.10 pattern | June 2026 pattern |
|---|---|---|
| Responsive widths | Hand-write widths={[400, 800, 1200]} + sizes | layout="constrained", Astro derives both |
| LCP image | loading="eager" + fetchpriority="high" separately | single priority prop |
| Encoder tuning | per-image quality everywhere | global image.service.config codecs (6.1) |
| Responsive styles + CSP | manual | hashed CSS classes, CSP-compatible out of the box |
layout accepts constrained, full-width, fixed, or none (default none). Note it is constrained, not “responsive” — that string does not exist. The densities prop is incompatible with layout; pick one.
How to tell you have an image problem
- Lighthouse flags “Properly size images” or “Serve images in next-gen formats”.
- Mobile LCP sits above the 2.5s “Good” threshold.
- Article pages ship 600KB+ of images per view.
- You see raw PNG screenshots that would be one-fifth the size as AVIF.
- Your bandwidth bill is dominated by
/_image?...requests hitting origin instead of the CDN.
Pattern 1: the modern body image
For in-article images, use <Picture> with layout="constrained". You no longer write widths or sizes by hand — Astro generates a sensible srcset from the source dimensions and the constrained layout:
---
import { Picture } from 'astro:assets';
import cover from '../assets/cover.png';
---
<Picture
src={cover}
alt="Diagram of the Astro build-time image pipeline"
layout="constrained"
formats={['avif', 'webp']}
decoding="async"
/>
constrained means the image scales down to fit its container but never renders larger than its intrinsic width — exactly what you want for body content in a fixed-width column. Without a layout, body images lazy-load by default (loading="lazy"), which is the correct behavior for everything below the fold.
If you skip layout and still want manual control, widths requires a matching sizes prop or Astro throws at build:
<Picture
src={cover}
alt="..."
widths={[400, 800, 1200]}
sizes="(max-width: 768px) 100vw, 800px"
formats={['avif', 'webp']}
/>
The cardinal rule either way: never generate a variant wider than the column can render. A 2400px AVIF for an 800px column is pure waste.
Pattern 2: the LCP image gets priority
The single image responsible for Largest Contentful Paint — your hero, cover, or above-the-fold diagram — must load eagerly. Lazy-loading it is still the most common LCP regression on content sites. As of Astro 5.10 you do not wire three attributes by hand; the priority boolean does it:
<Picture
src={hero}
alt="Article hero"
layout="full-width"
formats={['avif', 'webp']}
priority
/>
priority expands to loading="eager", decoding="sync", and fetchpriority="high". Use it on exactly one image per page — the LCP element. Adding it to multiple images defeats the purpose, because the browser can only meaningfully prioritize one.
If your hero renders deep inside a component the HTML parser does not reach early, add an explicit preload to the page <head> so the request starts during initial parse:
<link rel="preload" as="image" href={heroSrc.src} imagesrcset={heroSrcset} imagesizes="100vw" />
Pattern 3: tune the codecs once (Astro 6.1)
Before 6.1 you set quality per image and lived with whatever encoder effort Sharp defaulted to. Astro 6.1 lets you set codec defaults globally under image.service.config. Higher effort produces smaller files at the cost of build time — a real tradeoff once you have hundreds of images:
import { defineConfig } from 'astro';
export default defineConfig({
image: {
layout: 'constrained',
service: {
config: {
avif: { effort: 4, chromaSubsampling: '4:2:0' },
webp: { effort: 5 },
jpeg: { mozjpeg: true },
png: { compressionLevel: 9 },
},
},
},
});
Per-image quality still wins where you set it, so globals and overrides coexist. Setting image.layout here makes constrained the default for every <Image> and <Picture>, so you can drop the prop from each component.
Pattern 4: remote images and the CDN trap
Astro optimizes remote images too, but you must allow the host in astro.config.mjs:
export default defineConfig({
image: {
domains: ['images.unsplash.com'],
remotePatterns: [{ protocol: 'https' }],
},
});
Remote images go through the same build-time Sharp pass and land in dist/_astro/. If you deploy to a serverless host that does not persist dist/_astro/ correctly, you get broken images in production. Verify with a real deploy, not just npm run preview. Once live, make sure the optimized assets are served and cached at the CDN edge with a long TTL — letting /_image? query URLs hit origin on every view is the usual cause of a surprise bandwidth bill.
Common mistakes
- Using a raw
<img>with a PNG/JPG and skipping the pipeline. Astro cannot resize, transcode, or content-hash these. - Writing
widthswithout asizesprop. Astro errors at build; either addsizesor switch tolayout. - Passing
layout="responsive". That value does not exist; the responsive layout isconstrained. - Lazy-loading the LCP image, or putting
priorityon several images at once. - Combining
densitieswithlayout— they are mutually exclusive. - Optimizing screenshots as photos. Flat UI screenshots compress far better as PNG-8 or lossless AVIF than as default-quality WebP.
- Letting
_image?requests hit origin in production instead of caching at the CDN.
FAQ
- AVIF or WebP — which do I pick?: Ship both via
formats={['avif', 'webp']}. AVIF is the smaller format (20-30% under WebP at equal quality) and now covers ~95% of browsers; WebP (~97%) catches the rest and decodes faster on older hardware. The<img>fallback handles anything left. - Is the responsive
layoutprop still experimental?: No. It stabilized in Astro 5.10. You no longer need theexperimentalLayoutflag — setimage.layoutin config or passlayoutper component. - What exactly does
prioritydo?: It setsloading="eager",decoding="sync", andfetchpriority="high"together. Use it on the one above-the-fold LCP image only. - Does Astro handle animated WebP or GIF?: Sharp does not animate by default. Serve the original, or convert to MP4/WebM, which compress roughly 10x better than animated GIF.
- When is Cloudinary or imgix worth it over the built-in pipeline?: Past ~10,000 images, or when you need on-the-fly transforms the build can’t precompute. Below that, the build-time pipeline is simpler and free.
- Why did my build slow down after adding images?: Sharp is CPU-bound and AVIF
effortdominates. Lower AVIFeffortinimage.service.config, and cachenode_modules/.astro/between CI runs so unchanged images are not re-encoded.
External references: the Astro Images guide for the current props, and web.dev Optimize LCP for the preload reasoning behind Pattern 2.
Related
- Astro Content Collections — A 30-Minute Getting-Started
- Astro Incremental Content Update Without Full Rebuild
- Astro SEO basics
- When Astro is the right choice
Tags: #Indie dev #Astro #Performance #Core Web Vitals #images