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

Solve one concrete task

Post-process routes and assets

Add output-level processors for route artifacts and transformed assets without changing route handlers.

Add output-level processors

The framework has two processor hooks:

  • route processors, registered with processor(...)

  • asset processors, registered with asset_processor(...)

Use them when the concern is about the final output bytes rather than about how a route or image is authored.

Minify route HTML after rendering

Route processors implement ArtifactProcessor.

The simplest example is the built-in IntertagWhitespaceMinifier:

use site_core::{IntertagWhitespaceMinifier, Site};

let site = Site::builder()
    .processor(IntertagWhitespaceMinifier)
    .build()?;

That keeps route handlers focused on producing Artifact::Html(...) instead of mixing rendering with cleanup.

Write a custom route processor

For custom behavior, implement the trait yourself:

use anyhow::Result;
use site_core::{Artifact, ArtifactProcessor};

struct FeedBanner;

impl ArtifactProcessor for FeedBanner {
    fn name(&self) -> &'static str {
        "feed-banner"
    }

    fn process(&self, route_path: &str, artifact: Artifact) -> Result<Artifact> {
        match (route_path, artifact) {
            ("/feed.json", Artifact::Json(mut json)) => {
                json.push('\n');
                Ok(Artifact::Json(json))
            }
            (_, artifact) => Ok(artifact),
        }
    }
}

The processor sees the route path and the current artifact, so it can be selective without making that logic every route handler's problem.

If a processor returns a plain error, the framework wraps it in a structured diagnostic that names the processor and points back to the processor registration site.

Post-process asset outputs

Asset processors implement AssetProcessor.

They receive an AssetProcessContext plus the final output bytes:

use anyhow::Result;
use site_core::{AssetProcessContext, AssetProcessor};

struct StripDebugMarker;

impl AssetProcessor for StripDebugMarker {
    fn name(&self) -> &'static str {
        "strip-debug-marker"
    }

    fn process(&self, _context: &AssetProcessContext<'_>, bytes: Vec<u8>) -> Result<Vec<u8>> {
        Ok(bytes)
    }
}

Register it through the site builder:

.asset_processor(StripDebugMarker)

Plain asset-processor errors are also wrapped into structured diagnostics so the failing processor, source asset, and registration site are visible in terminal output.

Make processor cache behavior explicit

Asset processors participate in the asset cache key through cache_key().

If the processor behavior depends on options, include those options in the returned value:

impl AssetProcessor for StripDebugMarker {
    fn name(&self) -> &'static str {
        "strip-debug-marker"
    }

    fn cache_key(&self) -> String {
        "strip-debug-marker:v1".to_string()
    }

    fn process(&self, _context: &AssetProcessContext<'_>, bytes: Vec<u8>) -> Result<Vec<u8>> {
        Ok(bytes)
    }
}

Without that, the asset cache cannot tell that the processor configuration changed.

Choose the right layer

Use a route processor when the transformation is about a route artifact such as HTML, XML, JSON, or text.

Use an asset processor when the transformation is about one built asset file, such as a generated image or copied SVG.