# How to Optimize Images in Astro Markdown and Content Collections

Source: https://dteather.com/blogs/astro-optimized-images/
Author: David Teather
Published: 2024-07-26
Updated: 2026-07-05
Description: Optimize images in Astro Markdown and content collections with Astro's Image component, build-time WebP conversion, and lazy loading.
Categories: tutorials
Agent context: This is the Markdown source for a technical blog post. Use the source URL for citation, and use the HTML page when interactive demos, charts, images, or videos are relevant.

One of the easiest wins on an Astro site is moving images out of `public` and into the built-in image pipeline. That's what this post covers: regular images, blog images in content collections, and the handful of path/schema changes you need to make it all work. Once it's set up, Astro handles WebP conversion, dimensions, and lazy loading for you at build time.

If you're just interested in the code [here it is](https://github.com/davidteather/astro-search-optimized-image-blog-template) as a template website.

## What You Need

- An Astro site on version 3.0 or newer, that's when image optimization became built in
- Images currently living in your `public` folder
- Maybe 20 minutes depending on how many images you have to move

## Why Use Astro's Built-In Optimization?

The `public` folder contents are all directly copied over into the build folder without any pre-processing, so anything in there is served exactly as you saved it. A 2MB screenshot stays a 2MB screenshot. By moving images into `src` and using Astro's `<Image>` component you get, at build time:

1. Conversion to webp, which is usually a fraction of the size of the original png or jpg
2. Width and height attributes set automatically, which prevents layout shift while the page loads (Google cares about this for Core Web Vitals)
3. Lazy loading and async decoding by default

You could do this with manual Sharp scripts or an image CDN like Cloudinary, but Astro's built-in path is simpler and fits a static site better. No external service, no runtime work, and no extra moving parts once the migration is done.

## Statically Linked Images

First, we need to move all of our images that we're serving into `src` instead of in the `public` folder.

Anywhere in your code that you have statically defined links to images like

```html
<img src="/public/assets/my-image.png" alt="My alt" />
```

After moving the image from `public/assets` into `src/assets` we can replace it with the following.

```astro
---
import { Image } from "astro:assets";
import localBirdImage from '../../assets/my-image.png';
---
<Image src={localBirdImage} alt="My alt">
```

By using `<Image>` Astro will automatically optimize your image for the web at build-time, and that's all you have to do for statically linked images.

## With Dynamic Images From Blogs

This part is a little trickier because blog images aren't imported the same way as images in a normal component, but Astro gives you a clean way to handle it.

In your `src/content/config.ts` you might have something like this.

```ts title="src/content/config.ts"
const blog = defineCollection({
  schema: z.object({
    heroImage: z.string().optional(), // a url like /public/assets/heroImage.png
    // ... other fields
  }),
});
```

For us to be able to use the image in an `<Image>` component, we have to update the schema to be of the image type. This tells Astro to treat the field as an image import instead of a plain string.

```ts title="src/content/config.ts"
const blog = defineCollection({
  schema: ({ image }) =>
    z.object({
      heroImage: image().optional(),
      // ... other fields
    }),
});
```

Next for each of your blogs update your metadata to point to the image now in the src folder.

```yml
# From
heroImage: "/public/assets/blog-placeholder-3.jpg"
# To
heroImage: "../../assets/blog-placeholder-3.jpg"
```

Then update any references to images in your markdown itself. Once these are relative paths into `src`, Astro optimizes them automatically with no extra work.

```md
<!-- From -->

![blog placeholder](/public/assets/blog-placeholder-about.jpg)

<!-- To -->

![blog placeholder](../../assets/blog-placeholder-about.jpg)
```

After that, as we did before, update any references that were using `<img>` to use `<Image>`.

```astro
<!-- From -->
<img
    src={article.data.heroImage}
    alt=""
    data-pagefind-meta="image[src], image_alt[alt]"
/>
<!-- To -->
<Image
    src={article.data.heroImage}
    alt=""
    data-pagefind-meta="image[src], image_alt[alt]"
>
```

Then, Astro will automatically optimize all of your images!

## Troubleshooting

Some things that might trip you up along the way:

- `<Image>` requires an `alt` attribute and your build will fail without one. Annoying if you're converting a lot of images at once but it's good for accessibility and image SEO anyway, so it's wroth just writing them.
- If you get an error that Astro could not find the image, the relative path is wrong. The path in frontmatter or markdown is relative to that file, not to the project root, so a post in `src/content/blog/` reaching `src/assets/` needs `../../assets/`.
- Remote images (full `http` urls) aren't optimized by default. You have to authorize the domain in your astro config and provide dimensions, so this post's approach really only applies to images you host in the repo.
- Not everything should move out of `public`. Favicons and anything external services reference by a fixed url should stay there, since optimized image paths get hashed and change between builds.
- If you're using [a pagefind search bar](./2024-7-26-2024-astro-search-bar.md), the images in search results will look broken in `astro dev` after this change. Don't worry! They work once deployed, and you can verify locally with `astro preview`. The search index just gets built on the optimized image paths which change in dev.

And that's it, your images should now be a lot smaller. Here's a [template website](https://github.com/davidteather/astro-search-optimized-image-blog-template) with optimized images and a search bar. If you're building out an Astro blog I've also written about [adding interactive charts](./2024-10-29-astro-interactive-charts.md) and [adding Giscus comments](./2025-02-10-adding-giscus-to-astro.md).
