Docs

Images

Serve responsive images from static assets, without a build pipeline.

Open Markdown

Proa has no image optimization pipeline. It renders the <img> tag you write, with typed attribute support for srcset, sizes, imagesrcset, and imagesizes, and serves the files from your static directory.

src/components/hero.rs
html_sync! {
    <img
        src="/static/images/hero-800.avif"
        srcset="/static/images/hero-400.avif 400w,
                /static/images/hero-800.avif 800w,
                /static/images/hero-1600.avif 1600w"
        sizes="(max-width: 640px) 100vw, 800px"
        width="800"
        height="450"
        alt="Coastal cliffs at sunrise"
        loading="lazy"
        decoding="async"
    />
}

That is the whole API. What follows is how to make it fast.

The four attributes that matter

AttributeWhy
width and heightReserve the box before the bytes arrive. Without them the page reflows and your CLS score suffers
loading="lazy"Defer offscreen images. Never put it on your largest above-the-fold image
decoding="async"Keep decode work off the critical path
srcset + sizesLet the browser pick a file matching the device, instead of shipping a desktop image to a phone

srcset without sizes is a common mistake: the browser assumes the image is viewport-width and often picks a file larger than it needs.

Good to know: For the one image that is your Largest Contentful Paint, add fetchpriority="high" and leave loading off entirely. Lazy-loading your hero image is a measurable regression.

Generating the variants

Resizing is a build step you own. Generate the sizes ahead of time and commit them, or produce them in CI before proa build:

for w in 400 800 1600; do
  magick hero.jpg -resize ${w}x -quality 82 public/images/hero-${w}.avif
done

Prefer AVIF, then WebP, then JPEG. If you need per-format fallbacks rather than per-width, use <picture>:

src/components/hero.rs
html_sync! {
    <picture>
        <source srcset="/static/images/hero.avif" type="image/avif" />
        <source srcset="/static/images/hero.webp" type="image/webp" />
        <img src="/static/images/hero.jpg" width="800" height="450" alt="Coastal cliffs" />
    </picture>
}

Serving them

Images live under the directory your static service points at, public/ in scaffolded sites. The framework does not read a STATIC_DIR variable; Environment variables shows that as an app-level convention you can adopt. See Static assets for mounting and cache headers.

Give generated filenames a content hash and a long max-age. Stable names such as hero.avif need a shorter window or a cache-busting query string, because you cannot invalidate a name you keep reusing.

In MDX content, MdxImageConfig rewrites authored paths at build time, so /images/hero.avif in a Markdown file can resolve to /static/images/hero.avif in the output:

build.rs
.images(MdxImageConfig::new().rewrite_prefix("/images/", "/static/images/"))

Open Graph images

Social preview images are a different problem, generated per page, not authored. proa_og renders them headlessly. See Metadata and OG images.

Next steps

Search

Type at least 2 characters