Skip to content

Images & media

Every theme handles images two ways: public assets for raw files, and Astro’s <Image> for optimized images in components. This page covers both.

Drop files into public/. They’re served as-is at the matching URL:

public/
├── og.png → /og.png
├── favicon.svg → /favicon.svg
├── team/
│ └── alex.jpg → /team/alex.jpg
├── blog/
│ └── getting-started.jpg → /blog/getting-started.jpg
└── gallery/
├── 1.jpg → /gallery/1.jpg
└── 2.jpg → /gallery/2.jpg

Reference them by absolute path:

src/config/site.ts
team: {
members: [
{ name: "Alex Rivera", image: "/team/alex.jpg", /* … */ },
],
},
<img src="/blog/getting-started.jpg" alt="Aurora dashboard" />

Pros: zero config, predictable URLs. Cons: no automatic optimization — the browser downloads whatever you put there. Use this for already-optimized files (your og.png, favicon, SVG icons).

For photos and screenshots where you want WebP / AVIF, responsive sizes, and lazy loading, use Astro’s <Image> component.

src/components/Hero.astro
---
import { Image } from "astro:assets";
import heroImage from "~/assets/hero.png";
---
<Image
src={heroImage}
alt="Aurora — the modern platform"
width={1200}
height={800}
loading="eager"
/>

Important:

  1. The image must be imported as an ES module — Astro can only process images it can statically resolve at build.
  2. Files imported this way live in src/assets/ (or anywhere under src/), not public/.
  3. Astro generates WebP versions, hashes the filename, lazy-loads by default, and adds width/height to prevent layout shift.

In Markdown blog posts, ![alt](./image.jpg) works similarly — Astro processes relative paths:

src/content/blog/launch.md
---
title: "Launch"
image: "./launch-hero.jpg"
---
Welcome to Aurora.
![Aurora hero](./launch-hero.jpg)

Drop launch-hero.jpg next to launch.md in the same content folder.

SituationUse
Logo, favicon, OG imagepublic/
Team photos (in siteConfig.team)public/team/
Gallery photos (in siteConfig.gallery)public/gallery/
Blog post hero, in-post imagessrc/assets/ + <Image> (or relative ![]())
Hero / above-fold marketing imagessrc/assets/ + <Image>

The reason: data-driven sections (siteConfig.team, siteConfig.gallery) pass image paths as strings, so they can’t be statically imported. They stay in public/. Components you control directly can use <Image>.

Even with Astro’s <Image>, source files matter:

  • Photographs: 2× the display size, then export as JPG quality 80–85. Anything larger is wasted.
  • Screenshots: PNG if there’s text or sharp edges. JPG if it’s mostly a UI mock with photographic content.
  • OG cards: exactly 1200×630 px. The most common share-card size on Twitter, LinkedIn, Slack, iMessage.

Tools:

  • Squoosh — free, browser-based, supports AVIF / WebP / MozJPEG.
  • sharp CLI — already a dev dependency in most themes.

Several themes use Unsplash hotlinks in their demo data:

slides: [
{ image: "https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&auto=format&fit=crop&q=85" },
],

The ?w=1200&h=800&auto=format&fit=crop&q=85 query string is Unsplash’s image API — it serves a resized, optimized version. Useful for prototypes; replace with your own images before launch (Unsplash photos are free, but linking to their CDN couples your site to theirs).

Always set alt. It’s the single biggest accessibility win and it counts for image SEO.

<!-- Bad -->
<img src="/team/alex.jpg" />
<!-- Good -->
<img src="/team/alex.jpg" alt="Alex Rivera, CEO of Aurora" />
<!-- Decorative image — empty alt is correct -->
<img src="/dots.svg" alt="" aria-hidden="true" />

For background images in siteConfig blocks where there’s no alt field, add one to the schema:

images: [
{ src: "/gallery/1.jpg", alt: "Dashboard overview" },
],

Inline SVG is preferred over <img src="icon.svg"> — inline lets you set currentColor and animate stroke widths.

The bundled src/components/Icon.astro handles common icons. Add a new one by editing that file — paste your SVG path and add a name case.