RSTRust-First Site ToolkitA Rust-first static site generator with typed collections, explicit routes, and a format-agnostic asset pipeline.

Blog

Introducing the Asset Pipeline

The asset pipeline lets site code request explicit outputs while keeping source formats, deterministic caching, transforms, and adaptive HDR behind a smaller Rust API.

Ask for outputs, not source-specific operations

One of the design goals of this project is that route code should stay close to the intent of the site.

For assets, that means the site should ask for the output it wants:

  • an OpenGraph JPEG

  • a responsive AVIF image

  • a preserved SVG

It should not have to branch on whether the authored source is SVG, PNG, JPEG, WebP, or AVIF.

That is the job of the asset pipeline.

The public API surface is intentionally small

The main site-facing entry points are:

  • RenderContext::asset_url(...)

  • RenderContext::image_url(...)

  • RenderContext::responsive_image(...)

  • ImageOutput

  • ResponsiveImageOutput

Markdown components can use the same ideas through:

  • ComponentProps::asset_url(...)

  • ComponentProps::image_url(...)

  • ComponentProps::responsive_image(...)

That is the feature boundary the site author normally needs to learn. The same API serves frontmatter-authored assets and markdown-authored assets.

The input side is format-agnostic, but the output contract is explicit

The pipeline accepts several authored source formats today:

  • SVG

  • PNG

  • JPEG

  • WebP

  • AVIF

But the current public output-request contract is narrower on purpose:

  • ImageFormat::Original

  • ImageFormat::Svg

  • ImageFormat::Jpeg

  • ImageFormat::Avif

WebP is a supported input format, not a requested output format.

That distinction matters. The public API should not pretend a destination is supported when the engine would only reject it later at build time.

Route code asks for a destination, not an implementation recipe

The route asks for a destination shape:

let open_graph = ctx.image_url(
    entry,
    reference,
    &ImageOutput::raster(ImageFormat::Jpeg, 1200, 630)
        .with_fit(ImageFit::Cover)
        .with_background(BackgroundColor::rgb(248, 244, 234))
        .rasterize_vector_inputs(),
)?;

That code does not need to know whether the authored image started life as an SVG, an HDR AVIF, or something else.

The same idea applies to responsive imagery:

let hero = ctx.responsive_image(
    entry,
    reference,
    &ResponsiveImageOutput::widths(ImageFormat::Avif, [768, 1200, 1600])
        .with_sizes("(min-width: 54rem) 54rem, calc(100vw - 2rem)"),
)?;

Vector inputs stay vector by default

SVG is the best example of why this API shape matters.

In many sites, an authored SVG should stay SVG. The pipeline therefore preserves vector inputs by default even when the request is written through the image API.

Rasterization is explicit:

  • ordinary page imagery can stay vector

  • responsive raster outputs can opt in

  • raster-only destinations such as OpenGraph JPEGs can opt in

That keeps the common path unsurprising and avoids paying raster costs for a source that is already resolution-independent.

The cache key is based on behavior, not filenames

The cache identity includes:

  • the authored source bytes

  • a canonical transform description

  • any processor cache keys that affect final output bytes

That transform description is normalized before hashing, so equivalent requests produce the same cache key even when defaults were implicit in user code.

In practice that means the engine can safely reuse transformed objects across:

  • repeated builds

  • several pages requesting the same output

  • route and markdown paths that happen to converge on the same asset request

Probing and transforms are separate capabilities

Sometimes the route really does need information about the authored input before deciding which output to request.

That is why the API also exposes probing:

  • RenderContext::probe_image(...)

  • ComponentProps::probe_image(...)

The probe result gives you:

  • the authored format

  • dimensions

  • whether the source is vector-based

That is enough to make layout or width-selection decisions without decoding the full image payload.

Adaptive HDR stays inside the engine

The site-facing API does not expose an SDR/HDR switch.

If the source carries HDR semantics and the destination format can preserve them, the pipeline keeps that state internally. The current supported HDR-preserving output formats are:

  • AVIF

  • gain-map-capable JPEG output

If the pipeline can preserve the HDR base image but cannot safely derive every adaptive-HDR helper structure, it now prefers conservative degradation over bogus output. In that case the output is still produced and the build report surfaces a warning.

Build warnings expose degraded-but-non-fatal outcomes

Site::render_to(...) returns a BuildReport.

That report does not only tell you which routes and assets were rebuilt. It also now carries warnings for degraded outcomes that were still safe enough to ship.

The first concrete example is adaptive HDR:

  • if HDR AVIF output cannot safely synthesize a gain map, the pipeline preserves the HDR base image

  • if gain-map-capable JPEG output cannot be preserved safely, the pipeline emits a plain SDR JPEG instead

  • the build continues

  • the warning appears in BuildReport.warnings

That keeps the pipeline conservative without turning every imperfect third-party asset into a hard build failure.

Current non-goals

The current asset slice is intentionally opinionated about what it does not claim yet:

  • PNG is not a public output-request format

  • WebP is not a public output-request format

  • WebP gain-map support is not a current goal

  • selective rights-metadata preservation and injection are still future work

  • multi-input image composition is still future work

Those are not accidents. They are boundaries intended to keep the current API honest.

Where to go next