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 shipshero-avatar.jpgandabout-avatar.pnghere, plus ademo/folder of the sample post heroes and project thumbnails. Anything imported throughastro:assetsor referenced by animage()field goes undersrc/assets/, reachable via the@images/*and@assets/*aliases.src/data/<collection>/<slug>/— a content entry’s own images, sitting beside itsindex.mdx. A blog post’sheroImageand a project’sthumbnailare relative paths into the entry folder, validated by the collection schema’simage(). 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 shipsog.jpg(the fallback social image),favicon.svgandfavicon.icohere. Note there are no fonts inpublic/— 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. TheAvatarprimitive leans on this — pass it a bundled image and it emits the intrinsic dimensions; pass a URL string and it can’t.Avataralso requiresaltwheneversrcis set and throws at build if it is missing, so a missing alt is a build error, not a silent accessibility gap (passalt=""to mark an image decorative on purpose). - Open Graph dimensions.
BaseHeademitsog:image:widthandog:image:heightfrom a bundled image’s real dimensions; for thepublic/fallback it emits the 1200×630 convention, which is why the shippedog.jpgis 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:
public/og.jpg— the social preview. Replace with a real 1200×630 image; it is also the fallback for any page without its own.public/favicon.svgandpublic/favicon.ico— the browser tab icons.src/assets/images/andsrc/assets/images/demo/— the sample avatars and the demo post/project images. Replace them as you replace the sample content.- The content-entry images under
src/data/blog/*/andsrc/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.