Skip to content
AstroCraft Docs
On this theme

Images

Medice handles photography two different ways, and the split is not arbitrary — it follows from a constraint in the test runner.

Config data files cannot import an image

pnpm test runs each check as a plain Node module through type stripping. Node resolves neither the @images/* alias nor a .jpg import. So a config data file that imported its own photographs would be a file no check could import — and the config layer is exactly where the checks live.

The answer is src/config/siteImages.ts: one map from key to ImageMetadata. Data files carry keys; this map resolves them.

import sarahLindqvist from "@images/home/dr-sarah-lindqvist.jpg";
// …
const siteImages: Record<ImageKey, ImageMetadata> = { … };

Record<ImageKey, ImageMetadata> is the whole safety story: a key with no file, or a file with no key, fails astro check rather than rendering a blank space.

One map, not one per page

There is no such thing as a “homepage image”. Several photographs appear on two or more pages already — Dr. Lindqvist is the homepage hero, the fourth clinician card and the About story portrait — and a per-page map means writing those keys, imports and entries twice.

It also buys nothing measurable. An unused import in this file was measured against the build and emits no asset and no bytes, so splitting the map never tree-shook anything.

The directories under src/assets/images/about/, home/, services/ — record where each photograph first landed and stay a filesystem detail. The key is the name the rest of the codebase uses.

Collection covers live with their posts

Content collections have no such constraint, and image() is the platform feature for exactly this case:

src/data/blog/<slug>/index.mdx
src/data/blog/<slug>/cover.jpg    ← resolved by the schema's image()

So a health-library article’s cover is a sibling file rather than an entry in siteImages. Add a folder, put a cover in it, point heroImage at it.

heroImage is required on the blog schema, because every post is drawn as a card with a cover on the index and as an og:image in the head — a post without one is a hole in two places at once.

Optimization

Optimization happens at the call site through astro:assets, not in siteImages.ts, which only hands over the metadata.

<Image src={siteImages[photo]} alt={alt} widths={[400, 800, 1200]} />

<Image> emits a responsive, correctly-sized, modern-format asset with intrinsic dimensions, which is what keeps cumulative layout shift at zero without any CSS aspect-ratio work. sharp is a dev dependency for this.

Because the dimensions are real, BaseHead can emit og:image:width and og:image:height from the metadata rather than falling back to the 1200×630 convention.

Alt text

Alt text lives beside the image reference in the config data, not in the component. A clinician’s detail photo is { image, alt }; a gallery entry carries its own.

That placement is deliberate: the person writing the copy is the person who knows what the photograph shows, and a component cannot invent it. eslint-plugin-jsx-a11y is configured, so a missing alt on a raw <img> is a lint error.

Decorative images

The mint glow behind the hero is not an image at all. .decorative-glow reproduces the design’s exported blurred ellipse as a radial-gradient — same read, no asset request, and it follows a rebrand because the color is still the mint token.

SVG illustrations use currentColor and inherit a token from a text-* class on the root, so they cost no image request and re-theme for free. See Colors.

Icons inline into the HTML at build time — no sprite, no request, nothing in client JavaScript. See Icons.

The photography you must replace

This is the pre-launch item most likely to embarrass a buyer, so it is worth being blunt about it.

The specialist portraits are worse than placeholder. There are six photographs for twenty-seven slots, so a face recurs under different names across departments. A visitor comparing two specialty pages will see it.

servicesData.test.ts at least pins that a clinician the homepage also names keeps the homepage’s face, so the two pages agree while the photographs are still wrong — but agreement is not correctness here.

Also replace public/og.jpg with a real 1200×630 social image, and the favicons at public/favicon.svg and public/favicon.ico.

Adding a photograph

For a page or a config-driven card: drop the file under src/assets/images/, add an import and an entry to siteImages.ts, and add the key to the ImageKey union in src/config/types/configDataTypes.ts. Then reference the key from the data file. astro check will tell you if you missed a step.

For a health-library article: put it in the article’s own folder and point heroImage at it. Nothing else to update.

NEXT STEPComponents Reference