Skip to content
AstroCraft Docs
On this theme

Images & Assets

8-BitQuest keeps images in three places, and which one a file belongs in is decided by one question: does Astro need to optimize it, and does something need its real dimensions? Get that right and you avoid both layout shift and shipping a giant PNG unprocessed.

Where files live

  • src/assets/images/ — optimizable media that components import. The theme ships hero-avatar.jpg and about-avatar.png here, plus a demo/ folder of the sample post heroes and project thumbnails. Anything imported through astro:assets or referenced by an image() field goes under src/assets/, reachable via the @images/* and @assets/* aliases.
  • src/data/<collection>/<slug>/ — a content entry’s own images, sitting beside its index.mdx. A blog post’s heroImage and a project’s thumbnail are relative paths into the entry folder, validated by the collection schema’s image(). Keeping an entry’s images with the entry means moving or deleting a post takes its assets with it.
  • public/ — files served verbatim at the site root, unprocessed. The theme ships og.jpg (the fallback social image), favicon.svg and favicon.ico here. Note there are no fonts in public/ — the two typefaces are self-hosted through Fontsource and imported in CSS (see Typography).

Bundled versus public

This is the distinction that matters. An image imported through astro:assets or an image() schema field is a bundled ImageMetadata: Astro optimizes it at build, fingerprints its filename for caching, and — crucially — knows its intrinsic width and height. A path into public/ is a plain string: served as-is, no optimization, no dimensions.

The consequences show up in two places:

  • Layout shift. A bundled image contributes its real width/height, so the browser reserves the space and nothing jumps. The Avatar primitive leans on this — pass it a bundled image and it emits the intrinsic dimensions; pass a URL string and it can’t. Avatar also requires alt whenever src is set and throws at build if it is missing, so a missing alt is a build error, not a silent accessibility gap (pass alt="" to mark an image decorative on purpose).
  • Open Graph dimensions. BaseHead emits og:image:width and og:image:height from a bundled image’s real dimensions; for the public/ fallback it emits the 1200×630 convention, which is why the shipped og.jpg is exactly that size.

The rule of thumb: content and component images are bundled (src/assets/ or the entry folder); the OG fallback and favicons are public/, because they are referenced by literal root paths rather than imported.

Optimization

Bundled images are optimized at build by Astro’s default image service, which uses sharp. sharp is not a direct dependency — it comes with Astro and ships prebuilt binaries, so even though the workspace denies its native build script (to keep installs fast), optimization still runs. If you ever deploy to a platform without a prebuilt binary and see images passing through unprocessed, pnpm add sharp installs it directly. See Installation.

One primitive opts out on purpose: CardImage takes a raw or remote src string, for cases where the image is not a bundled asset. When you do have an optimizable asset, the guidance in the file is to skip CardImage and drop an astro:assets <Image> in its place, so you get the optimization and the dimensions.

Responsive images

Where the theme renders a bundled image at different sizes across breakpoints, it passes widths and sizes so the browser picks the right file — the theme card in the docs and the content cards follow this pattern. You rarely write it by hand for content, because the card components handle it; when you build a new image-bearing section, copy the pattern rather than shipping one oversized file to every viewport.

What to replace before launch

The shipped images are placeholders. Before you deploy:

  1. public/og.jpg — the social preview. Replace with a real 1200×630 image; it is also the fallback for any page without its own.
  2. public/favicon.svg and public/favicon.ico — the browser tab icons.
  3. src/assets/images/ and src/assets/images/demo/ — the sample avatars and the demo post/project images. Replace them as you replace the sample content.
  4. The content-entry images under src/data/blog/*/ and src/data/projects/*/ — each post’s hero and each project’s thumbnail, swapped out with the entries themselves.

Because every content image is validated by an image() schema field, a broken or missing path fails the build with the entry named — so you will hear about a forgotten image at pnpm build, not from a broken picture in production.

NEXT STEPComponents Reference