statica
Just HTML. A blazingly fast static site generator for valid HTML.
Full reference: docs/guide.md
Install
Homebrew:
See homebrew/README.md for tap setup.
JavaScript (npm):
Create a new site directly from npm:
Rust (crates.io):
From this repo (dev):
Quick start
statica new creates a small localized starter with a shared layout, i18n catalogs, the statica badge, and copyable valid HTML.
Project layout
statica routes are folders, content is linked at build time, layouts hold shared page shells, and fragments hold reusable components.
my-site/
├── statica.toml
├── index.html
├── 404/index.html
├── content/
│ ├── posts/
│ └── i18n/en.json
├── layouts/
│ └── base.html
├── ui/
│ └── post-card.html
├── posts/[slug]/index.html
├── blog/[page]/index.html
└── public/
└── logo.svg
public/, assets/, and static/ are copied by default through asset_dirs.
New projects keep reusable page chrome in layouts/base.html, route-specific content in **/index.html, reusable component templates in ui/, and funnel data in content/.
CLI
statica [PATH] build + watch + serve (default)
statica build [PATH] one-off build
statica serve [PATH] preview latest build
statica watch [PATH] watch mode
statica new <NAME> scaffold
statica -h / --help
statica -v / --version
Options
Config (statica.toml)
Optional. Missing file → defaults. See docs/guide.md for the full reference.
= "" # relative to this file; empty = here
= ".website"
= ["public", "assets", "static"]
= "" # needed for sitemap / RSS
= false
[]
= "@"
[]
= "https://fonts.googleapis.com/css2"
[]
= "./static"
= "./ui"
[]
= false
= true
= true
= true
= false
[]
= [480, 768, 1024, 1366, 1920]
= ["webp"]
= 85
= "100vw"
= true
[]
= false
= true
= true
= true
[]
= false
= 50000
[[]]
= 10
= "published_at"
= true
= true
[]
= false
= 50
[]
= false
= "search.json"
[]
= "auto"
= 0
[]
= "0.0.0.0"
= 4321
[]
= false
= "formspree"
[]
= false
= ["en"]
| Asset kind | Tool |
|---|---|
| CSS | lightningcss (nesting, modern syntax → browser-ready; minify with --process or --minify) |
| JS | oxc |
| HTML | minify-html (final pass with --minify) |
| Images | oxipng + image |
| Fonts | copied as-is |
Inline <style> (pages + fragments) is always transformed. Linked .css under asset_dirs is transformed when [process].css is on. Enable [minify] / --minify for a final pass on emitted HTML, CSS, and linked JS; inline scripts are preserved so scoped fragment behavior stays exact.
Set [performance].render_mode to auto, serial, or parallel. serial avoids rayon for page rendering; parallel always uses rayon; auto uses statica's default page-render profile. Use render_threads = 0 for the default worker count, or set --render-threads N to cap parallel page rendering.
Use --report-json [PATH] to write the build report as JSON for benchmarks, CI, and integrations. Omit PATH or pass - to write JSON to stdout; pass a file path to update that file. In watch, the report is written after the initial build and each rebuild.
statica watch performs conservative incremental rebuilds. Direct edits to an existing page index.html re-emit only that page route when global post-processing is off. Changes to shared inputs such as data, fragments, assets, config-driven processing, deleted files, or minified builds fall back to a full rebuild.
Authoring
statica source is valid HTML. It uses normal <template>, <slot>, and <link> elements as build-time authoring primitives, so keep them where HTML allows them.
Pages and routes
Every index.html is a page. Folder names become routes, and bracket folders expand from build-time data.
index.html → .website/index.html
404/index.html → .website/404/index.html
posts/[slug]/index.html → .website/posts/{item.slug}/index.html
blog/[page]/index.html → .website/blog/1/, blog/2/, … ([[pagination]])
[locale]/about/index.html → .website/en/about/, .website/pt/about/ ([i18n])
Static pages emit once. Collection pages use a bracket param such as [slug] and a linked data array; the current record is item. Pagination pages use [page] plus [[pagination]]; page metadata and items live under page.pagination.
404
If the site does not define 404.html or 404/index.html, statica writes a default .website/404/index.html. Custom 404 pages are normal source pages and always win. statica serve returns the 404 page with HTTP status 404 for missing paths.
Data funnels
Data funnels load content at build time with <link rel="statica/data">. href points to a file or explicit glob, and id names the data in the page or fragment scope.
Supported sources are JSON, JSONL/NDJSON, CSV, plain text, Markdown, and globs of those files. Data is loaded during the build; production pages should not fetch site content at runtime.
Dynamic data href values use the same scoped attribute rules, so locale data can use paths like href="../content/posts.${i18n.locale}.json" after binding {i18n}.
Binding basics
Use data-bind to declare scope, data-t to replace text, and ${...} inside attributes.
Post
Post
Read
- Scalar page text →
data-t="${item.field}", or literal text withdata-t="Plain text" - Attributes →
${item.slug}/${page.pagination.next_href}/${i18n.locale} - Page
data-binddeclares canonical roots such as{item},{page},{data}, or{i18n}before use - Data link IDs are directly available by
id; they cannot be nameddata,item,page, ori18n - Placeholders must be dotted identifier paths; statica does not evaluate JavaScript expressions
Fragments
Fragments are build-time HTML components. Import a fragment file, mount it with a matching <slot id>, and define a <template> with the same id.
<!-- page -->
<!-- ui/post-card.html -->
Post
Read
Fragment mounts pass the current context. data-each loops over an array and passes each item. Fragments never receive canonical page context automatically; pass values through the mount context or link fragment-local data.
Fragment scripts are scoped by default. Inside a fragment <script>, document.querySelector, document.querySelectorAll, and document.getElementById search only that fragment instance.
Layouts
Layouts are build-time document shells. A page declares one layout with <link rel="statica/layout">; statica loads that layout, projects page content into layout slots, then continues normal data, fragment, binding, asset, and minify steps.
<!-- layouts/base.html -->
Fallback nav
<!-- index.html -->
Home
Home
Hello
Page <head> children project into <slot name="head">. Page body children without slot project into the default layout slot. Body elements with slot="name" project into matching named slots; <template slot="name"> projects its children without keeping the template wrapper.
The generated starter and examples/blog use this shape: layouts/base.html owns shared metadata, global styles, navigation, search, and footer; route pages import it and keep only page-specific head entries and body content.
Aliases
Aliases allow short prefixes instead of repeating long local paths or URLs. They are configured in statica.toml, and the default leading symbol is @.
Use aliases anywhere statica resolves authoring paths, such as fonts, scripts, styles, fragments, data funnels, and assets.
[aliases.urls] entries resolve to absolute URLs. [aliases.paths] entries resolve to local paths relative to statica.toml. The text after the alias name is preserved as the tail, so @static/app.js resolves against the static alias base.
CSS, JS, images, and assets
Inline <style> in pages and fragments is always transformed with lightningcss. Linked .css under asset_dirs is transformed when [process].css is enabled.
When [process].enabled and [process].images are on, statica optimizes copied raster images, writes responsive width variants, adds configured formats such as WebP, and rewrites local <img> tags to responsive <picture> markup when [process.image].responsive is true. Use [process.image] to control widths, formats, JPEG quality, and the default sizes value.
Use <link rel="statica/font"> for font stylesheets. Google Fonts URLs get the expected preconnect hints once per page.
Search
Add a generated browser-side search modal with one authoring input.
statica emits /search.json and small runtime files under /statica/. Configure the index with [search], or use --search 'output=assets/search.json' from the CLI.
Forms
Mark static forms with statica, then configure a provider endpoint. Formspree is the default provider.
Send
[]
= true
= "formspree"
= "https://formspree.io/f/{id}"
[]
= "your-form-id"
i18n
Use a [locale] route segment and enable [i18n]. Catalogs live at content/i18n/{locale}.json by default.
[]
= true
= ["en", "pt"]
= "en"
Home
Home
Pages must bind {i18n} before using catalog values. Fragments do not receive i18n automatically.
Deploy
statica build writes plain static files to .website/ by default. Deploy that output directory to any static host.
License
MIT
Author
(c) 2026 Simão Nziaka