Skip to main content

Crate ssg

Crate ssg 

Source
Expand description

SSG logo

Static Site Generator (SSG)

AI-augmented, accessibility-first static site generator built in Rust.

Build Crates.io Docs.rs Coverage lib.rs License: MIT OR Apache-2.0 MSRV 1.88.0 OpenSSF Scorecard OpenSSF Best Practices


§Contents

Getting started

  • Install — Cargo, Homebrew, apt, AUR, container, one-liner, make install
  • Quick Start — scaffold, build and serve a site in five commands

Reference

Operational


§Install

[dependencies]
ssg = "0.0.63"

§Prebuilt binaries

# macOS / Linux -- one command
curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/static-site-generator/main/scripts/install.sh | sh

# Homebrew
brew install --formula https://raw.githubusercontent.com/sebastienrousseau/static-site-generator/main/packaging/homebrew/ssg.rb

# Cargo
cargo install ssg

# Debian / Ubuntu (amd64 or arm64)
sudo dpkg -i ssg_0.0.63_amd64.deb

# Arch Linux (AUR)
yay -S ssg

# Container
docker pull ghcr.io/sebastienrousseau/static-site-generator:latest

§Build from source

git clone https://github.com/sebastienrousseau/static-site-generator.git
cd static-site-generator
make          # check + clippy + test

Requires Rust 1.88.0+. Tested on Linux, macOS, and Windows.

§System install (packagers)

make install                      # to /usr/local
make PREFIX=/usr install          # to /usr
make DESTDIR=/tmp/stage install   # staged, for packaging
make uninstall                    # exact inverse of install

Installs the binary, the ssg.1 man page and bash, zsh and fish completions to their FHS locations. The man page and completions are generated from the CLI definition at build time, so man ssg cannot drift from ssg --help; CI asserts that on every push. PowerShell completions are generated too, and installed when PWSHCOMPDIR is set.


§Quick Start

# 1 -- Install
cargo install ssg

# 2 -- Scaffold a new site
ssg -n mysite -c content -o build -t templates

# 3 -- Build
ssg -c content -o public -t templates

# 4 -- Development server with live reload
ssg -c content -o public -t templates -s public -w

# 5 -- AI-powered readability fix (requires Ollama)
ssg -c content -o public -t templates --ai-fix

§Overview

SSG generates static websites from Markdown content, YAML frontmatter, and MiniJinja templates. It compiles everything into production-ready HTML with built-in SEO metadata, WCAG 2.2 AA accessibility compliance (including the new 2.5.8, 2.4.13, 3.2.6 criteria where automatable), multilingual readability scoring, and feed generation. The 32-plugin pipeline handles the rest.

  • 32-plugin pipeline – SEO, a11y, i18n, search, images, AI, CSP, JSON-LD, RSS, sitemaps
  • Agentic AI pipeline – audit, diagnose, fix, and verify content readability via local LLM
  • Multilingual readability – Flesch-Kincaid (EN), Kandel-Moles (FR), Wiener Sachtextformel (DE), Gulpease (IT), LIX (SV), Fernandez Huerta (ES)
  • Incremental builds – content fingerprinting via FNV-1a hashing and dependency graph
  • Bounded-memory batch compilation – configurable memory budgets for 100K+ page sites
  • WCAG 2.2 AA – accessibility checked on every build (non-blocking by default; reports written to accessibility-report.json + wcag-compliance.json) and gated in CI by axe-core. Build-failure on a11y violations is opt-in via the STRICT_A11Y env var (implemented in v0.0.40)
  • Zero unsafe code – #![forbid(unsafe_code)] across the entire codebase

§Why this approach?

SSG occupies the niche Hugo, Zola and Eleventy occupy — Markdown and templates in, static HTML out — and differs in one respect: the checks most generators leave to a deployment pipeline run inside the compiler, on every build.

Three choices follow from that, and each has a cost worth stating:

  1. Accessibility and security are build stages, not CI steps. WCAG checks, CSP extraction with SRI hashing, and the 15-gate audit runner execute during ssg build. A page that fails is reported with its file and line, not discovered after deployment. The cost is build time: a fully-gated build does more work than a generator that only renders. STRICT_A11Y decides whether a violation warns or fails.

  2. No async runtime. The crate is deliberately tokio-free; parallelism is Rayon over a work-stealing pool, and the LLM transport is synchronous ureq. This keeps the dependency tree small and the binary self-contained. The cost is that genuinely concurrent I/O — the dev server, the HMR socket — uses threads rather than tasks, which is heavier per connection than an async runtime would be. The reasoning is recorded in docs/adr/.

  3. #![forbid(unsafe_code)] workspace-wide. No unsafe block exists in the tree. Memory-safety review is therefore a property of the compiler rather than of code review, and the Miri job (.github/workflows/miri.yml) checks the dependency surface rather than our own. The cost is that a few optimisations available through raw pointers are simply unavailable.

§Compared with other generators

The table below is a capability comparison, not a benchmark; for build times see Benchmarks.

CapabilitySSGHugoZolaAstro
Agentic AI pipelineYesNoNoNo
Multilingual readabilityYesNoNoNo
Auto-generated OG imagesYesNoNoPlugin
Built-in WCAG validationYesNoNoNo
CSP/SRI auto-extractionYesNoNoPlugin
axe-core CI gateYesNoNoNo
WebAssembly targetYesNoNoN/A
98% CI coverage floorsYesNoNoNo
Zero unsafe codeYesYesYesN/A

§Architecture

graph TD
    A[Content: Markdown + YAML] --> B{SSG CLI}
    B --> V[Content Schema Validation]
    V --> C[Incremental Cache + `DepGraph`]
    C --> D[Compile: staticdatagen]
    D --> E[Post-Processing Fixes]
    E --> F[Fused Transform Pipeline: 32 plugins]
    F --> G[Output: HTML + RSS + Atom + Sitemap + JSON-LD]
    B --> H[File Watcher + CSS HMR]
    H -->|changed files| C
    B -->|--serve| S[Dev Server + Live Reload + Error Overlay]
    B -->|--ai-fix| AI[Agentic LLM Pipeline]
    AI -->|audit + fix| A

§Benchmarks

MetricValue
Source143,000+ lines across 10 workspace crates (ssg, ssg-core, ssg-a11y, ssg-i18n, ssg-search, ssg-mcp, ssg-rpc, ssg-rpc-macro, ssg-wasm, and the unpublished ssg-heap-probe)
Test suite3,668 unit tests (cargo test --lib) + 61 integration test targets
Coverage98% region, 98% line, 98% function (CI-gated); measured 99.31 / 99.29 / 99.24
Plugin pipeline32 plugins, Rayon-parallelised
Audit gates15 (WCAG 2.2 AAA, JSON-LD, hreflang, lang consistency, CSP+SRI, PQC TLS, HTML5, broken links, OG, markdown lint, perf budget, AI discovery, RSS/Atom, image opt, search index integrity)
Examples8 branded sites + 2 edge-runtime adapters (Cloudflare Workers, Vercel Edge)
Edge runtimesCloudflare Workers + Vercel Edge with ISR (ssg-wasm)
SearchBrowser-native int8 vector embeddings via ssg-search (WASM, ≤2 MB gzipped budget)
MSRVRust 1.88.0

§Build performance

PagesTimeMemory
100< 5s (CI-gated)< 100 MB
1,000< 10s< 200 MB
10,000Bounded-memory batches512 MB budget
100,000+Bounded-memory batchesConfigurable via --max-memory

Reproduce: cargo bench --bench bench -- scalability.


§Features

PerformanceParallel file operations with Rayon, fused single-pass HTML transforms, content-addressed caching (FNV-1a), dependency graph for incremental rebuilds, bounded-memory batch compilation for 100K+ pages, --jobs N thread control, --max-memory MB budget
AI PipelineAgentic LLM pipeline (--ai-fix): audit content readability, diagnose failing files, generate fixes via local LLM (Ollama), verify improvement, produce JSON report. Dry-run mode (--ai-fix-dry-run). Auto-generate alt text, meta descriptions, and JSON-LD via LLM
ReadabilityMultilingual scoring: Flesch-Kincaid (EN), Kandel-Moles (FR), Wiener Sachtextformel (DE), Gulpease (IT), LIX (SV/NO/DA), Fernandez Huerta (ES). BCP 47 language detection from frontmatter. CI readability gate
ContentMarkdown with GFM extensions (tables, strikethrough, task lists), YAML/TOML/JSON frontmatter, typed content schemas with compile-time validation, shortcodes (youtube, gist, figure, admonition), compile-time word count + estimated reading time injected into .meta.json sidecars
SEOMeta description, Open Graph (title, description, type, url, image, locale), auto-generated OG social cards (SVG), Twitter Cards, canonical URLs, robots.txt, sitemaps with per-page lastmod, first-class topic-cluster taxonomy (hub + pillar indexes), related-post discovery via Jaccard tag/category overlap
Structured DataJSON-LD Article/WebPage with datePublished, dateModified, author, image, inLanguage, BreadcrumbList
SyndicationRSS 2.0 with enclosures and categories, Atom 1.0, Google News sitemap
AccessibilityWCAG 2.2 AA validation on every build (1.1.1, 1.3.1, 2.3.1, 2.4.4, 2.4.13, 2.5.8, 3.1.1, 3.2.6), axe-core Playwright CI, decorative image detection, heading hierarchy, ARIA landmarks; emits wcag-compliance.json matrix (WCAG 2.2 + EAA guide)
i18nHreflang injection, x-default support, per-locale sitemaps, language switcher HTML. Translated slugs — pages pair across locales by a translation_key front-matter field rather than by identical path, so /about/ and /fr/a-propos/ are reciprocal alternates; pages without a key keep pairing by path. The default locale may serve from the site root (/about/ alongside /fr/a-propos/), matching Hugo, Astro and Next.js (i18n guide)
ImagesResponsive <picture> with WebP sources, srcset at 320/640/1024/1440, lazy loading, CLS prevention, optional cdn_prefix for serving local image assets from a CDN host
TemplatesMiniJinja engine with inheritance, loops, conditionals, custom filters
SearchClient-side full-text search with modal UI, 28 locale translations, Ctrl+K / Cmd+K
SecurityCSP build-time extraction (zero unsafe-inline), SRI hash generation, asset fingerprinting, path traversal prevention, structured SsgError type-safe error hierarchy
MinificationNative HTML / JS / CSS minification, always on and written in-tree — no minify-html, oxc_minifier or lightningcss. A minifier rewrites every byte the generator emits, so it is kept where this crate’s own tests cover it. <pre>, <textarea>, <script> and <style> content survives byte for byte, attribute values keep their spacing, and comments are preserved. Recursive walk processes every .html, .css and .js file under site_dir regardless of depth.
Supply ChainAutomated CycloneDX 1.5 SBOM (sbom.cdx.json) generated on every build via SbomPlugin, listing compiler version, dependency tree, and license metadata
DXCSS hot reload, browser error overlay via WebSocket, file watching with change classification
WebAssemblyssg-core + ssg-wasm + ssg-search compile to wasm32-unknown-unknown with wasm-bindgen; ssg-wasm ships ISR + RPC entry points for Edge runtimes (CI-enforced ≤ 2 MB gzipped)
Vector searchssg-search — browser-native int8-quantised hashed-n-gram (or opt-in model2vec-rs) embeddings, Float32Array boundary, no division / sqrt at runtime, p99 < 100 ms on 1000-doc corpus (CI-gated)
Edge runtimesCloudflare Workers + Vercel Edge adapters with KV / Edge Config content provider, SHA-256-keyed ISR manifest, invalidation webhook, optional View Transitions client (transitions = true)
Edge RPC#[ssg_rpc] proc-macro, JSON-over-POST dispatch, schemars 1.2 + custom JSON-Schema → TypeScript emitter, golden .d.ts test
Edge headersPer-host emitters for Cloudflare _headers, Netlify _headers, Vercel vercel.json with PQC posture guidance — emits current best-practice TLS/PQC configuration guidance for your CDN (X25519+ML-KEM-768 hybrid notes); ML-DSA content-provenance signing is roadmap (#579)
Audit CLIssg audit runs 15 gates (WCAG 2.2 AAA, JSON-LD, hreflang, lang consistency, CSP+SRI, PQC TLS, HTML5, broken links, OG, markdown lint, perf budget, AI discovery, RSS/Atom, image opt, search index integrity); JSON / JUnit / SARIF v2.1.0 / text outputs (v0.0.45 #562, GitHub Code Scanning ingestible)
Architecture Decision RecordsSix baseline ADRs under docs/adr/ in Nygard format documenting the tokio-free architecture, Rayon orchestration, lol_html selection, sync tungstenite HMR, ureq LLM transport, and CycloneDX-over-SPDX SBOM choice. CI-enforced adr: ADR-NNNN citation graph (v0.0.45 #557)
Supply-chain attestationcargo-vet (v0.0.45 #561) layers per-crate audit attestation over cargo deny’s license + CVE checks. Imports Mozilla Firefox, Bytecode Alliance, and Google trust sets; exemption-reduction policy in supply-chain/README.md
Concurrency proofsMiri job (.github/workflows/miri.yml, v0.0.45 #560) runs cargo miri test --lib on a nightly schedule + run-miri-labelled PRs. Loom + Kani follow in v0.0.46 (#564 / #565)
Feature-matrix CIcargo hack check --feature-powerset --depth 2 (v0.0.45 #584) exercises every reachable subset of {ai, benchmark, cli, image-optimization, otel, templates, test-fault-injection} on every PR — catches cfg-gating gaps before they merge
Agentic discoveryOpt-in /agents.txt (robots-style AI agent allow/deny), /.well-known/ai-plugin.json (OpenAI plugin manifest), /.well-known/mcp.json (Model Context Protocol registry with auto-populated resources)
ISO 20022 JSON-LDSchema.org descriptors for regulated financial sites: BankAccount, FinancialProduct, MonetaryAmount, PaymentInstrument, RegulatedFinancialInstitution. Built-in IBAN + BIC validators
View TransitionsOpt-in (transitions = true) View Transitions API client + lazy hydration; persistent <header> / <footer> get view-transition-name so they don’t animate across boundaries; falls back to plain reload in non-supporting browsers
IslandsWeb Components with lazy hydration (visible, idle, interaction)

§The CLI

ssg ships with both a unified subcommand surface (introduced in v0.0.43) and the legacy bare-flag pipeline (preserved with a deprecation warning, removal in 1.0).

Usage: ssg [COMMAND]

Commands:
  build    Produce a static site under the configured output directory
  dev      Start the dev server with file watching and HMR
  check    Run all build-time validators without writing output
  plugins  Inspect the plugin pipeline
  audit    Run the 15 native audit gates against the built site
  deploy   Build the site and ship to a pluggable target
  help     Print this message or the help of the given subcommand(s)

§Build flags

      --incremental        Skip recompile when DepGraph diff is empty (issue #524)
      --isr                Emit an SHA-256-keyed ISR manifest for Edge adapters (issue #549)
      --no-llm-cache       Bypass the deterministic LLM inference cache (issue #528)

§Legacy flags (still supported)

  -f, --config <FILE>      Configuration file path
  -n, --new <NAME>         Create new project
  -c, --content <DIR>      Content directory
  -o, --output <DIR>       Output directory
  -t, --template <DIR>     Template directory
  -s, --serve <DIR>        Start development server (HMR + livereload)
  -w, --watch              Watch for changes and rebuild
  -j, --jobs <N>           Rayon thread count (default: num_cpus)
      --max-memory <MB>    Peak memory budget for streaming (default: 512)
      --ai-fix             Run agentic AI pipeline to fix content readability
      --ai-fix-dry-run     Preview AI fixes without writing changes
      --validate           Validate content schemas and exit
      --drafts             Include draft pages in the build
      --deploy <TARGET>    Generate deployment config (netlify, vercel, cloudflare, github)
  -q, --quiet              Suppress non-error output
      --verbose            Show detailed build information
  -h, --help               Print help
  -V, --version            Print version

§Library Usage

Minimal pipeline
fn main() -> Result<(), ssg::error::SsgError> {
    ssg::run()
}
Custom plugin
use ssg::plugin::{Plugin, PluginContext, PluginManager};
use ssg::error::SsgError;
use std::path::Path;

#[derive(Debug)]
struct LogPlugin;

impl Plugin for LogPlugin {
    fn name(&self) -> &str { "logger" }
    fn after_compile(&self, ctx: &PluginContext) -> Result<(), SsgError> {
        println!("Site compiled to {:?}", ctx.site_dir);
        Ok(())
    }
}

fn main() -> Result<(), SsgError> {
    let mut pm = PluginManager::new();
    pm.register(LogPlugin);
    pm.register(ssg::plugins::MinifyPlugin);

    let ctx = PluginContext::new(
        Path::new("content"),
        Path::new("build"),
        Path::new("public"),
        Path::new("templates"),
    );
    pm.run_after_compile(&ctx)?;
    Ok(())
}
Content schema validation

Create content/content.schema.toml:

[[schemas]]
name = "post"

[[schemas.fields]]
name = "title"
type = "string"
required = true

[[schemas.fields]]
name = "date"
type = "date"
required = true

[[schemas.fields]]
name = "draft"
type = "bool"
default = "false"

Pages with schema = "post" in their frontmatter are validated at compile time. Run ssg --validate for schema-only checks.

Readability audit
use ssg::llm::{LlmPlugin, LlmConfig};
use std::path::Path;

let report = LlmPlugin::audit_all(Path::new("content"), 8.0).unwrap();
println!("{}/{} files pass grade 8.0", report.passing, report.total_files);

// Multilingual: French content uses Kandel-Moles automatically
// when frontmatter contains `language: fr`
OG image generation
use ssg::og_image::generate_og_svg;

let svg = generate_og_svg("My Page Title", "My Site", "#1a1a2e", "#ffffff");
std::fs::write("og-card.svg", svg).unwrap();
// 1200x630 SVG social card ready for og:image
Vector search index (issue #545)
use ssg_search::artifacts::{Artifacts, InputDoc};
use ssg_search::VectorEngine;

// Build side: turn a doc corpus into the 4-file artifact blob.
let docs = vec![
    InputDoc {
        url: "/post-1".into(),
        title: "Rust WebAssembly".into(),
        body: "rust compiles to wasm".into(),
        excerpt: "rust wasm".into(),
    },
    InputDoc {
        url: "/post-2".into(),
        title: "Sourdough".into(),
        body: "starter flour water salt".into(),
        excerpt: "bread".into(),
    },
];
let arts = Artifacts::from_docs(&docs);
std::fs::write("site/search/embeddings.bin", &arts.embeddings).unwrap();
std::fs::write("site/search/model.bin", &arts.model).unwrap();
std::fs::write("site/search/tokenizer.bin", &arts.tokenizer).unwrap();

// Runtime (native or WASM): load and query.
let engine = VectorEngine::new(
    &arts.model, &arts.tokenizer, &arts.embeddings, arts.count(),
).unwrap();
let top = engine.search("rust webassembly", 3);
println!("{:?}", top);
Edge RPC method (issue #548)
ⓘ
use ssg_rpc::ssg_rpc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, JsonSchema)]
struct EchoIn { msg: String }

#[derive(Serialize, JsonSchema)]
struct EchoOut { msg: String }

#[ssg_rpc]
fn echo(input: EchoIn) -> Result<EchoOut, ssg_rpc::DispatchError> {
    Ok(EchoOut { msg: input.msg })
}

// The build emits `site/rpc.d.ts` automatically; clients call:
//   const r = await ssgRpc("echo", { msg: "hi" });
Audit a built site (issue #551)
ssg audit site/ --out junit > audit.xml
# 15 gates run: WCAG 2.2 AAA, JSON-LD, hreflang, lang consistency, CSP+SRI, PQC TLS,
# HTML5, broken links, OG, markdown lint, perf budget, AI discovery,
# RSS/Atom, image opt, search index integrity.

§Examples

Run any example:

cargo run --example basic
cargo run --example blog
ExamplePurpose
basicMinimal site with SEO, search, and fused transforms
quickstartScaffold and build in 10 lines
multilingualMulti-locale site with hreflang and per-locale sitemaps
multilingual_fullNested content/<lang>/ layout, plus translated slugs paired by translation_key (/en/about/ ↔ /fr/a-propos/ ↔ /de/ueber-uns/). Asserts reciprocity, not just that pages rendered
pluginsFull plugin pipeline demo with DepGraph API
blogEAA-compliant accessibility-first blog with dual feeds
docsDocumentation portal with schema validation and syntax highlighting
landingZero-JS landing page with CSP hardening
portfolioDeveloper portfolio with JSON-LD and Atom feed

§Edge-runtime adapters

examples/edge-cloudflare/ and examples/edge-vercel/ are runnable reference implementations of the ISR + RPC pipeline for the two target edge runtimes. Each contains a wrangler.toml / vercel.json, the TypeScript glue (worker.ts, api/[...path].ts), an upload script for content provisioning (KV / Edge Config), and a smoke-test harness.

# Cloudflare Workers
cd examples/edge-cloudflare
npm install
npm run upload:kv          # uploads site/ to KV namespace
npx wrangler deploy

# Vercel Edge
cd examples/edge-vercel
npm install
npm run upload:edge-config # uploads site/ to Edge Config
vercel deploy

§Themes

A theme is a directory of layouts and the assets they reference. Name one and SSG resolves it:

# ssg.toml
theme = "quill"
ssg build --theme quill

Either form sets the template directory for you, so a project does not have to hand-write a path into someone else’s tree — a path that breaks when the theme moves and says nothing about which theme it is.

Where themes are looked for, in order:

  1. themes/ beside the ssg.toml that names the theme, so a build does not depend on the directory it was invoked from
  2. themes/ in the working directory
  3. each entry of SSG_THEME_PATH

Inside a theme, either _layouts/ or templates/ is accepted. Which convention a theme follows is its author’s business, not its consumer’s.

Precedence is --template > --theme > theme = in the config. Naming a theme and a template directory together is how you override one layout without forking the theme.

A name that does not resolve reports every directory searched and the themes that do exist. A directory that exists but holds no layouts says that instead, and says what it expected to find.

The nine published themes — apex, atlas, kaishi, kinetic, lucid, quill, stablo, velocity, voxt — live at ssg-themes.

§Stability guarantees

§What a version bump promises

This project is 0.0.x until 0.0.999 (ADR-0009). Under Cargo’s semantic-versioning rules every 0.0.z release is its own compatibility island, so any release may contain a breaking change. That is stated plainly rather than implied: pin an exact version if you need stability, and read the CHANGELOG before upgrading.

What the project does instead of a promise it cannot yet keep:

  • cargo-semver-checks runs on every push against the published baseline, so an accidental API break is caught and has to be deliberate.
  • Every breaking change is called out in CHANGELOG.md under the release that made it, with the migration.

§The output-stability rule

For a generator the public API is not only the Rust signatures — it is what the tool emits. A change to generated HTML, a meta tag, a sitemap, a feed or a JSON-LD block is a breaking change even when no function signature moves, because it lands in someone’s deployed site and their diff.

So output changes are treated as breaking:

  • They are listed in the CHANGELOG like any other break.
  • tests/golden_files.rs pins generated artefacts against checked-in snapshots, so an unintended change to output fails CI rather than shipping.
  • Refresh a snapshot deliberately with UPDATE_GOLDEN=1; the diff is then part of the review.

§Deprecation window

A deprecated flag, config key or API keeps working for two releases after the one that deprecates it, and warns when used, naming its replacement. Removal happens no earlier than the third release and is listed in the CHANGELOG.

The legacy top-level CLI flags are the current example: still accepted, still tested, and warning via LEGACY_DEPRECATION_WARNING.


§Minimum supported Rust version

Rust 1.88.0.

§The policy

The MSRV is a floor set by dependencies, not a target chosen independently. It currently comes from time-macros, staticdatagen and the oxc_* crates.

  • It may rise in any release, because a 0.0.z release carries no compatibility promise (see above).
  • It rises only when a dependency this project already needs requires it, or when a language feature removes real complexity — never incidentally.
  • Each rise is recorded in the CHANGELOG for that release, with the reason and the crate that forced it.
  • The floor is CI-enforced: scheduled.yml builds against [stable, 1.88], so a change that quietly needs a newer compiler fails rather than being discovered by a user.

§On distro compatibility

This project does not claim compatibility with any distribution’s packaged Rust. That claim would need a table mapping current distro toolchains to this floor, kept current — and an unverified claim there is worse than none, because it is exactly what a packager would rely on. If you need SSG on a distro toolchain, check rust-version in Cargo.toml against your rustc --version, then build from source or use a prebuilt binary.


§When not to use SSG

Stated plainly, so the answer is not discovered halfway through a migration:

  • You need server-rendered, per-request pages. SSG compiles ahead of time. Islands hydrate on the client and ssg-rpc reaches an edge function, but the page itself is a file. An application whose HTML depends on the requesting user is the wrong shape for it.
  • You want a large theme ecosystem. The theme suite ships nine first-party themes. Hugo has thousands. If picking a ready-made theme matters more than the build-time gates, Hugo is the better answer.
  • Your content lives in a CMS you cannot export. ContentProvider abstracts the source and is used for Cloudflare KV and Vercel Edge Config, but there is no turnkey adapter for a hosted CMS today; you would be writing it.
  • You need a stable plugin API right now. The Plugin trait is usable and documented, but it is pre-1.0 and can change between releases. Third-party plugins are compiled in, so a plugin author builds their own binary until the WASM runtime lands.
  • Build time matters more than build-time checking. Accessibility, CSP extraction and the audit gates are work a renderer-only generator does not do. That is the trade the design makes; if it is the wrong trade for you, it is the wrong tool.

§Development

make              # check + clippy + test
make test         # run all tests
make bench        # run Criterion benchmarks
make lint         # lint with Clippy
make format       # format with rustfmt
make deny         # supply-chain audit
make doc          # build API documentation
make a11y         # run axe-core accessibility audit
make clean        # remove build artifacts

§CI

WorkflowTriggerPurpose
ci.ymlpush, PRfmt, clippy, test (3 OS), docs lint, install contract, coverage (98% floor), cargo-deny, cargo-vet
document.ymlpush to mainBuild and deploy API docs to GitHub Pages
release.ymltag v*Cross-platform binaries, GHCR container, crates.io, AUR
scheduled.ymlweekly, tagMulti-OS portability, axe-core a11y, CycloneDX SBOM, benchmarks
visual.ymlPRPlaywright screenshots (3 viewports) + axe-core WCAG 2.2 AA
wasm.ymlpush, PRBuild and test ssg-core + ssg-wasm for wasm32
readability-gate.ymlPRFlesch-Kincaid audit on docs and content

See DEVELOPMENT.md for the full developer guide — notably the table mapping every CI job to the exact command that reproduces it locally, which is CI-checked against the workflow. See CONTRIBUTING.md for signed commits and PR guidelines.


§Security

Safety guarantees and compliance
  • #![forbid(unsafe_code)] across the entire codebase
  • CSP build-time extraction: all inline styles and scripts moved to external files with SRI hashes. Zero unsafe-inline
  • Path traversal prevention with .. detection and symlink rejection
  • File size limits and directory depth bounds
  • cargo audit with zero advisories
  • cargo deny – license, advisory, ban, and source checks
  • cargo vet – per-crate audit attestation; imports Mozilla Firefox, Bytecode Alliance, and Google trust sets. See supply-chain/README.md
  • CycloneDX SBOM generated as release artifact with Sigstore attestation
  • SPDX license headers on all source files
  • Signed commits enforced via SSH ED25519

See docs/whitepaper/csp-without-compromise.md for the full CSP security architecture.

All 69 modules
ModulePurpose
accessibilityWCAG 2.2 AA checker, ARIA validation, decorative image detection, target-size + focus-appearance checks, compliance matrix
aiAI-readiness hooks, alt-text validation, llms.txt / llms-full.txt generation
assetsAsset fingerprinting and SRI hash generation
cacheContent fingerprinting (FNV-1a) for incremental builds
cmdCLI argument parsing, SsgConfig, input validation
contentContent schemas, ContentValidationPlugin, --validate
cspCSP build-time extraction of inline styles/scripts to external files
depgraphPage-to-template dependency graph for incremental rebuilds
deployNetlify, Vercel, Cloudflare Pages, GitHub Pages adapters
draftsDraft content filtering
frontmatterFrontmatter extraction and .meta.json sidecars
fs_opsSafe file operations with traversal prevention
highlightSyntax highlighting for code blocks
i18nHreflang injection, per-locale sitemaps, language switcher
image_pluginResponsive <picture> with WebP, srcset generation
islandsWeb Components with lazy hydration
livereloadWebSocket live-reload, CSS HMR, browser error overlay
llmLocal LLM pipeline (Ollama), multilingual readability scoring, agentic fix
loggingStructured logging configuration
markdown_extGFM tables, strikethrough, task lists
og_imageAuto-generated OG social card SVGs
paginationPagination plugin for listing pages
pipelineBuild orchestration, plugin registration, RunOptions
pluginPlugin trait with lifecycle hooks, PluginManager, PluginCache
pluginsMinifyPlugin, ImageOptiPlugin, DeployPlugin
postprocessSitemap, RSS, Atom, manifest, HTML post-processing fixes
processDirectory creation and site processing
scaffoldProject scaffolding (ssg --new)
schemaJSON Schema generator for configuration
searchFull-text search index, modal UI, 28 locale translations
seoSeoPlugin, JsonLdPlugin, CanonicalPlugin, RobotsPlugin
serverDevelopment HTTP server
shortcodesyoutube, gist, figure, admonition expansion
streamHigh-performance streaming file processor
streamingBounded-memory batch compiler for large sites (--max-memory budget)
taxonomyTag and category index generation
listingsNamed, filtered, paginated listings
topic_clustersCurated pillar-page metadata for topics
template_engineMiniJinja templating engine integration
template_pluginMiniJinja template rendering plugin
themeResolves a theme name to its layout directory, for theme = / --theme
walkShared bounded directory walkers
watchPolling-based file watcher with change classification
event_watchEvent-driven file watcher (notify::recommended_watcher, 100 ms debounce) — issue #526
hmrComponent-level Hot Module Replacement over sync WebSocket (tungstenite) with discriminated hmr-css / hmr-html / reload frames — issue #526
llm_cacheDeterministic content-hash-keyed LLM inference cache at target/ssg-cache/llm/ (atomic writes, --no-llm-cache to bypass) — issue #528
isr_manifestPer-page SHA-256 manifest for Edge ISR adapters (--isr flag) — issue #549
view_transitionsOpt-in View Transitions API client + lazy-hydration emitter (transitions = true) — issue #547
rpc_schema#[ssg_rpc] JSON-Schema → TypeScript .d.ts emitter — issue #548
search_indexBuild-side emitter for ssg-search artifacts (embeddings.bin, model.bin, tokenizer.bin, manifest.json) — issue #545
postprocess::edge_headersPer-host header emitters (Cloudflare _headers, Netlify _headers, Vercel vercel.json) with PQC posture guidance — issue #550
postprocess::agentic_discovery/agents.txt + /.well-known/{ai-plugin.json,mcp.json} emitters — issue #552
seo::jsonld::iso20022ISO 20022 schema.org descriptors for regulated financial sites (IBAN/BIC validators) — issue #553
audit15-gate audit runner (ssg audit) with JSON / JUnit / text output — issue #551
bench_corpusDeterministic seeded corpora for benchmarking; byte-identical across machines and releases
agent_apiAgent JSON API emitter — issue #586
collectionsTyped content collection API — issue #456
content_stagerContent staging; isolates residual upstream compiler gaps
datesDependency-free date parsing shared by feeds and sitemaps
deploy_adapterDeploy adapter trait + per-target stubs for ssg deploy
dev_serverDev-server glue wiring EventWatcher → DepGraph — issue #526
errorError types and context extension traits
io_poolBounded writer-thread pool decoupling disk writes from rayon
oembedoEmbed 1.0 emitter — issue #586
otelOpenTelemetry build-pipeline tracing scaffolding — issue #422
sbomBuild-time CycloneDX SBOM generation — issue #457
theme_manifestTheme manifest compatibility check
urlsCanonical page-URL derivation shared by staging, feeds and SEO
utilCross-cutting utilities, including the lol_html head walker

§Workspace crates

CratePublishedPurpose
ssg✓ crates.ioMain library + binary. Imports the other crates.
ssg-core✓ crates.ioWASM-compatible core: markdown compile, frontmatter parse, ContentProvider trait, ISR manifest types. Shared by build + Edge runtimes.
ssg-wasmInternalwasm32-unknown-unknown entry points for Cloudflare Workers + Vercel Edge (ISR + RPC dispatch). Built via wasm-pack.
ssg-searchInternalBrowser-native vector semantic search. Int8-quantised hashed-n-gram embedder by default (model-free, deterministic); opt-in real model2vec-rs encoder.
ssg-rpcInternalJSON-over-POST RPC dispatch + schemars 1.2 + custom TS emitter. Paired with #[ssg_rpc] proc-macro for zero-config method registration.
ssg-rpc-macroInternalProc-macro for #[ssg_rpc] attribute. Registered into a global inventory at compile time.

§Documentation

DocumentContents
BENCHMARKS.mdPerformance methodology, CI budgets, comparison tables, and the latest nightly figures
DEVELOPMENT.mdToolchain setup, running every CI gate locally, test layout, release model
docs/ARCHITECTURE.mdHow the build pipeline fits together, for contributors
docs/packaging.mdFor distribution maintainers: licence grant, toolchain floor, offline builds, install layout, signature verification
SECURITY.mdReporting policy, supported versions, guarantees
CONTRIBUTING.mdDevelopment workflow and review expectations
docs/adr/Architecture Decision Records in Nygard format
docs/guide/Configuration, content, i18n, search, SEO, deployment
supply-chain/README.mdcargo-vet policy and exemption burn-down
docs/SUMMARY.mdUser Manual — every guide, reference and decision, rendered with mdbook serve
docs.rs/ssgGenerated API reference

§License

Dual-licensed under Apache 2.0 or MIT, at your option.

See CHANGELOG.md for release history.

Back to Top

Re-exports§

pub use error::PathErrorExt;
pub use error::SsgError;
pub use staticdatagen;

Modules§

accessibility
Automated WCAG accessibility checker and ARIA validation plugin.
agent_api
Agent JSON API emitter (issue #586, port 3 of 5).
ai
AI-readiness content hooks.
assets
Asset optimization: fingerprinting, SRI hashes, and basic minification.
audit
Native CI audit gates (issue #549).
audit_plugin
Automated 10-Pillar Quality Gate and Master Compliance Audit Plugin.
bench_corpus
Deterministic synthetic corpora for benchmarking.
cache
Content fingerprinting for incremental builds.
cmd
Command Line Interface Module
collections
Typed content collection API (issue #456).
content
Typed content collections with frontmatter schema validation
content_stager
Content staging — residual upstream-gap workarounds for the staticdatagen → staticweaver → metadata-gen pipeline.
csp
Content Security Policy hardening plugin.
dates
Flexible, dependency-free date parsing shared by the feed and sitemap post-processing plugins.
depgraph
Page dependency graph for incremental rebuilds.
deploy
Deployment adapter generation.
deploy_adapter
Deploy adapter trait + per-target stubs for the ssg deploy subcommand (issue #527 AC4).
dev_server
Dev-server glue (issue #526) — wires EventWatcher → DepGraph → HmrBroadcaster into one loop.
drafts
Draft filtering plugin.
error
Error handling types and context extension traits for the SSG library.
event_watch
Event-driven file watcher (issue #526).
frontmatter
Shared frontmatter extraction and .meta.json sidecar support.
fs_ops
File system operations: directory copying, safety validation, and traversal.
highlight
Syntax highlighting plugin.
hmr
Hot-module-reload protocol for ssg dev (issue #526).
i18n
Internationalisation (i18n) routing primitives
image_plugin
Image optimization plugin.
io_pool
Bounded writer-thread pool that decouples disk writes from rayon CPU workers (issue #569, phase 1).
islands
Resumable hydration — interactive islands (Web Components).
isr_manifest
ISR manifest emitter — dist/.ssg/manifest.json + raw content KV payloads under dist/.ssg/content/.
listings
Named, filtered, paginated listings (#587).
livereload
Live-reload script injection plugin.
llm
Local LLM content plugin.
llm_cache
Deterministic content-hash-keyed cache for local LLM inference (issue #528).
logging
Logging infrastructure for the static site generator.
markdown_ext
GitHub Flavored Markdown (GFM) extensions plugin.
oembed
oEmbed 1.0 emitter (issue #586, port 4 of 5).
og_image
Auto-generates Open Graph social card images from page metadata.
otel
OpenTelemetry build-pipeline tracing scaffolding (issue #422).
pagination
Pagination plugin.
pipeline
Build pipeline: plugin orchestration and site compilation.
plugin
Plugin architecture for SSG
plugins
Built-in plugins
postprocess
Post-processing plugins that fix staticdatagen output.
process
Argument-driven site processing.
rpc_schema
Edge RPC schema emitter (issue #548 AC1 + AC4).
sbom
Build-time SBOM generation (issue #457).
scaffold
Project scaffolding.
schema
Configuration Schema Generator
search
Client-side search index generator.
search_index
Vector-search artifact emitter (issue #545).
seo
SEO plugins for the static site generator.
server
Dev server infrastructure for the static site generator.
shortcodes
Shortcode expansion plugin.
stream
High-performance streaming file processor.
streaming
Streaming compilation for large sites.
taxonomy
Taxonomy generation plugin.
template_engine
Template engine integration (MiniJinja).
template_plugin
Template rendering plugin.
theme
Theme resolution.
theme_manifest
Theme manifest compatibility check.
topic_clusters
Curated metadata for the topics taxonomy (#587).
urls
Canonical page-URL derivation shared by staging, feeds, and SEO output (spec A2/B1, plan §2 item 1.2, issue #586).
util
Cross-cutting utility modules.
view_transitions
View Transitions API + lazy navigation (issue #547).
walk
Shared bounded directory walkers.
watch
File-watching module for the static site generator.

Structs§

HttpTransport
Production transport: starts an http_handle::Server.
Paths
Represents the necessary directory paths for the site generator.
PathsBuilder
Builder for creating Paths configurations

Constants§

MAX_DIR_DEPTH
Maximum directory nesting depth for all traversal operations. Prevents stack overflow from pathological or circular directory trees. 128 levels accommodates any realistic project structure.

Traits§

ServeTransport
Pluggable transport that drives the dev server.

Functions§

collect_files_recursive
Recursively collects all file paths within a directory.
compile_site
Compiles the static site from source directories.
copy_dir_all
Recursively copies a directory whilst maintaining structure and attributes.
copy_dir_all_async
Asynchronously copies an entire directory structure, preserving file attributes and handling nested directories.
copy_dir_with_progress
Copies directories with a progress bar for feedback.
create_directories
Creates and verifies required directories for site generation.
create_log_file
Creates and initialises a log file for the static site generator.
execute_build_pipeline
Runs the build half of the pipeline: before_compile → compile → after_compile. Does not start the dev server.
generate_locale_redirect
Generates a root index.html that reads the browser’s language preference and redirects to the best matching locale directory.
handle_server
Configures and launches the development server.
is_path_within_root
Checks that path resolves to a location inside root.
is_safe_path
Checks if a given path is safe to use.
log_arguments
Logs processed command-line arguments for debugging and auditing.
log_initialization
Records system initialisation in the logging system.
now_iso
Returns the current time as an ISO 8601 UTC string.
prepare_serve_dir
Prepares the serve directory by creating it and copying site files.
run
Executes the static site generation process.
serve_site
Converts a site directory path to a string and starts an HTTP server.
serve_site_with
Starts the dev server using a caller-supplied transport.
verify_and_copy_files
Validates and copies files from source to destination.
verify_and_copy_files_async
Asynchronously validates and copies files between directories.
verify_file_safety
Verifies the safety of a file for processing.