Data, content, and assets
Use local images
Import source images and let Astro determine dimensions and create optimized output.
Import an image from src/ and Astro takes over. It reads the dimensions, optimizes the file, and writes the img tag for you.
Here is the Image component from astro:assets:
---
import { Image } from 'astro:assets'
import workshop from '../assets/workshop.jpg'
---
<Image
src={workshop}
alt="Two developers sketching a data flow on a whiteboard"
widths={[480, 800, 1200]}
sizes="(max-width: 700px) 100vw, 700px"
/>
Build the site and look at the output. It’s along these lines:
<img
src="/_astro/workshop.Cx9Ah2Kd_1a2b3c.webp"
srcset="/_astro/workshop.Cx9Ah2Kd_Z1qT8y.webp 480w, … 1200w"
sizes="(max-width: 700px) 100vw, 700px"
alt="Two developers sketching a data flow on a whiteboard"
width="1600"
height="1067"
loading="lazy"
decoding="async"
/>
Notice what you didn’t write. width and height come from the file. Astro read them at build time, and because they’re in the HTML, the browser reserves the space before the image loads. No layout jump.
The format changed to WebP. The file name has a hash, so it can be cached forever. loading="lazy" and decoding="async" are defaults. The widths array became a srcset, so a phone downloads the 480px version, not the 1200px one.
public is different
An image in public/ skips all of this. Reference it with a root URL:
<img src="/logo.svg" alt="Astro notes" width="120" height="40" />
Astro copies the file as-is. No optimization, no dimension detection, and you write width and height yourself or accept layout shifts. Use public/ for a favicon, an OG image that must keep an exact URL, or a file you don’t want touched. For everything else, import from src/.
Alt text
The alt attribute describes what the image is for, here. Not the filename. “Two developers sketching a data flow” tells a screen reader user what the picture adds to the paragraph. alt="workshop.jpg" tells them nothing.
For a decorative image, one that adds nothing the text doesn’t already say, use alt="". That’s a deliberate signal to skip it.
Image requires alt. Leave it out and the build fails:
[ImageMissingAlt] Image missing required "alt" property.
I like that error. It’s a lot cheaper than an accessibility audit.
Rename the file
Now rename workshop.jpg to whiteboard.jpg without touching the import, and build. It fails immediately, pointing at the import line. Compare that to the public/ version: rename logo.svg, and the build passes. The 404 shows up in production, in a visitor’s browser.
That’s the whole argument for importing. Broken references become build errors instead of surprises.
Lesson completed