Images
Large photos are the main reason pages are slow. The Image component from the astro:assets module shrinks an image during the build, converts it to the lightweight WebP format and sets its dimensions so the page does not "jump" while loading. For this the image must live in src/, not in public/.
An image on a page
Put a photo at src/assets/anna.jpg and open the page src/pages/about.astro (if you have about.md, create a test page src/pages/photo.astro next to it):
---
import { Image } from 'astro:assets';
import anna from '../assets/anna.jpg';
---
<Image src={anna} alt="Anna at her laptop" width={400} />The image is imported as a module, so Astro knows its original size. The page shows a photo 400 pixels wide. If you view the page source in the browser, you see an <img> tag pointing to a .webp file, with width, height and loading="lazy" attributes.
Image attributes
| Attribute | What it does |
|---|---|
src | An imported image or an address |
alt | A text description, required |
width, height | The desired size; Astro computes the other one |
format | Output format, webp by default |
loading | lazy by default; eager for the first image on screen |
Without alt the build stops with an error: Astro cares about accessibility. A decorative image gets alt="".
Images from another site
For an image given by a full address Astro does not know the size, so width and height are set by hand:
<Image src="https://example.com/cover.jpg" alt="Cover" width={800} height={450} />For such images to be optimized too, allow the domain in astro.config.mjs with image: { domains: ['example.com'] }. Otherwise Astro outputs the image as is.
A post cover
In a collection an image can be stored next to the post. The schema receives an image() helper that checks the path and turns it into an import:
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: ({ image }) => z.object({
title: z.string(),
cover: image().optional(),
// other fields as in the collections lesson
}),
});In the post frontmatter you write a path relative to the file: cover: ./first-post.jpg. On the post page src/pages/posts/[id].astro the cover is rendered like this: {post.data.cover && <Image src={post.data.cover} alt="" width={720} />}.
Several formats
The Picture component from the same module outputs a <picture> tag with several variants, for example formats={['avif', 'webp']}. The browser picks a format it understands.
Common mistake
People put an image in public/ and pass it to Image as the string /anna.jpg. Astro does not process files from public/: the size is unknown, the build demands width and height, and there is no optimization. Keep images for Image in src/assets/ and bring them in with import.