---
title: "Astro: Image Optimization"
slug: "astro-image-optimization"
category: "frontend"
tags: ["frontend", "astro", "images", "performance", "avif", "webp"]
status: "stable"
last_updated: 2026-05-14
summary: "Use Image and Picture from astro:assets for local images, configure remote domains, and serve AVIF/WebP with correct dimensions to prevent layout shift."
related: ["[[frontend/astro]]", "[[frontend/astro-performance]]", "[[seo/page-speed]]", "[[seo/image-seo]]", "[[frontend/astro-integrations]]", "[[comparisons/nextjs-vs-astro]]"]
---

## Overview

Astro's built-in image optimization runs through `astro:assets`. The `<Image />` component transforms local images at build time: it generates AVIF and WebP variants, sets explicit `width` and `height` to prevent layout shift, and writes a responsive `srcset` when you pass `widths`. No external service is needed for local images. For remote images, you must allowlist the hostname; Astro fetches and optimizes them at build time (static) or request time (SSR).

## Use `<Image />` for local images with a single output size

Import the image file into the component and pass it to `src`. Astro infers `width` and `height` from the source file and generates the optimized output format.

```astro
---
import { Image } from "astro:assets";
import hero from "../assets/hero.jpg";
---
<Image src={hero} alt="A description of the image" />
```

By default, Astro converts to WebP. Pass `format="avif"` to force AVIF output. AVIF gives 30 to 50 percent smaller files than WebP at the same quality but takes longer to encode; use it for production builds.

## Use `<Picture />` for responsive images with multiple sizes

`<Picture />` generates a `<picture>` element with `<source>` tags for each format and a `<img>` fallback. Pass `widths` and `sizes` to control the responsive behavior.

```astro
---
import { Picture } from "astro:assets";
import hero from "../assets/hero.jpg";
---
<Picture
  src={hero}
  widths={[400, 800, 1200]}
  sizes="(max-width: 720px) 100vw, 800px"
  formats={["avif", "webp"]}
  alt="Hero image"
/>
```

The browser receives AVIF for browsers that support it, WebP for browsers that do not, and the original JPEG as a final fallback. Always pass both `widths` and `sizes` together; without `sizes`, the browser assumes the image is 100vw.

## Allowlist remote image domains in `astro.config.mjs`

Remote images require explicit domain allowlisting. Add the `image.domains` array to your config.

```js
// astro.config.mjs
export default defineConfig({
  image: {
    domains: ["images.contentfulassets.com", "cdn.sanity.io"],
  },
});
```

For broader patterns, use `image.remotePatterns` with a `hostname` and optional `protocol` and `pathname` glob. Astro rejects remote image URLs not covered by `domains` or `remotePatterns` at build time.

## Set the LCP image to eager loading with high fetch priority

The Largest Contentful Paint image should not be lazy-loaded. Pass `loading="eager"` and `fetchpriority="high"` to the LCP image.

```astro
<Image
  src={hero}
  alt="Hero"
  loading="eager"
  fetchpriority="high"
/>
```

All other images below the fold default to `loading="lazy"`. Astro does not set `loading` automatically; you must pass it explicitly when you want eager loading.

## Store source images in `src/assets/`, not `public/`

Images in `public/` are copied verbatim to the build output. Astro does not transform them. Put source photos, illustrations, and diagrams in `src/assets/` so the optimizer runs. Use `public/` for pre-sized assets: favicons, OG images already at 1200x630, and images referenced by external URLs.

A common mistake is placing a large hero JPEG in `public/` and wondering why Lighthouse reports a 3 MB image. Move it to `src/assets/` and import it with `<Image />`.

## Astro Image vs Next/Image

Both optimize images at build or request time, generate `srcset`, and set dimensions. The differences:

- Astro works at build time for static output; no server is needed. Next.js `<Image>` optimizes at request time via an API route.
- Astro `<Picture />` exposes format selection directly. Next.js uses `next.config.js` `images.formats` globally.
- Next.js `<Image>` has a `placeholder="blur"` prop that generates a base64 LQIP automatically. Astro does not have a built-in LQIP; generate the placeholder with a build script or a Vite plugin and pass it as `style`.

For LQIP in Astro, generate a base64 string during the build and inline it:

```astro
<Image
  src={hero}
  alt="Hero"
  style={`background-image: url('${lqipBase64}'); background-size: cover;`}
/>
```

## Related

- [[frontend/astro]]
- [[frontend/astro-performance]]
- [[seo/page-speed]]
- [[seo/image-seo]]
- [[frontend/astro-integrations]]
- [[comparisons/nextjs-vs-astro]]
