statica 0.51.3

A blazingly fast static site generator that builds on just HTML
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# statica

**Just HTML.** A blazingly fast static site generator for valid HTML.

Full reference: [docs/guide.md](docs/guide.md)

## Install

**Homebrew:**

```bash
brew tap akaizn-junior/statica
brew install statica
```

See [homebrew/README.md](homebrew/README.md) for tap setup.

**JavaScript (npm):**

```bash
npm i -D @statica/cli
npx statica
```

Create a new site directly from npm:

```bash
npm create statica@latest my-site
cd my-site
statica
```

**Rust (crates.io):**

```bash
cargo install statica-cli --locked
```

From this repo (dev):

```bash
cargo install --path crates/statica-cli --force
```

## Quick start

`statica new` creates a small localized starter with a shared layout, i18n catalogs, the statica badge, and copyable valid HTML.

```bash
npm create statica@latest my-site
cd my-site
statica                 # build, watch, and serve cwd
```

## Project layout

statica routes are folders, content is linked at build time, layouts hold shared page shells, and fragments hold reusable components.

```text
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

```text
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

```bash
statica build --rss 'title=Blog,limit=20,collections=posts'
statica build --sitemap 'filename=sitemap.xml,urls_per_file=50000'
statica build --process 'css=true,js=false,images=true'
statica build --minify 'html=true,css=true,js=true'
statica build --process --minify
statica build --search 'output=assets/search.json'
statica build --pagination 'page_size=10,sort_desc=true,index=true'
statica build --i18n 'locales=en|pt,default=en'
statica build --render-mode serial
statica build --report-json report.json
statica watch --preview host=127.0.0.1,port=9000
```

## Config (`statica.toml`)

Optional. Missing file → defaults. See [docs/guide.md](docs/guide.md) for the full reference.

```toml
project = ""                 # relative to this file; empty = here
out_dir = ".website"
asset_dirs = ["public", "assets", "static"]
site_url = ""                # needed for sitemap / RSS
manifest = false

[aliases]
symbol = "@"

[aliases.urls]
Google = "https://fonts.googleapis.com/css2"

[aliases.paths]
static = "./static"
ui = "./ui"

[process]
enabled = false
css = true
js = true
images = true
fonts = false

[process.image]
widths = [480, 768, 1024, 1366, 1920]
formats = ["webp"]
quality = 85
sizes = "100vw"
responsive = true

[minify]
enabled = false
html = true
css = true
js = true

[sitemap]
enabled = false
urls_per_file = 50000

[[pagination]]
page_size = 10
sort_by = "published_at"
sort_desc = true
index = true

[rss]
enabled = false
limit = 50

[search]
enabled = false
output = "search.json"

[performance]
render_mode = "auto"
render_threads = 0

[preview]
host = "0.0.0.0"
port = 4321

[forms]
enabled = false
provider = "formspree"

[i18n]
enabled = false
locales = ["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.

```text
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.

```html
<link rel="statica/data" href="content/posts/*.md" id="posts" />
<link rel="statica/data" href="content/vehicles.csv" id="vehicles" />
<link rel="statica/data" href="content/notes.txt" id="notes" type="text/plain" />
```

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.

```html
<html lang="en" data-bind="{item}">
  <head>
    <title data-t="${item.headline}">Post</title>
  </head>
  <body>
    <h1 data-t="${item.headline}">Post</h1>
    <a href="/posts/${item.slug}/">Read</a>
  </body>
</html>
```

- Scalar page text → `data-t="${item.field}"`, or literal text with `data-t="Plain text"`
- Attributes → `${item.slug}` / `${page.pagination.next_href}` / `${i18n.locale}`
- Page `data-bind` declares canonical roots such as `{item}`, `{page}`, `{data}`, or `{i18n}` before use
- Data link IDs are directly available by `id`; they cannot be named `data`, `item`, `page`, or `i18n`
- 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`.

```html
<!-- page -->
<link rel="statica/data" href="content/posts/*.md" id="posts" />
<link rel="statica/fragment" type="text/html" href="ui/post-card.html" id="post-card" />
<slot id="post-card" data-each="posts"></slot>
```

```html
<!-- ui/post-card.html -->
<template id="post-card" data-bind="{slug, headline}">
  <article>
    <h2 data-t="${headline}">Post</h2>
    <a href="/posts/${slug}/">Read</a>
  </article>
</template>
```

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.

```html
<!-- layouts/base.html -->
<html lang="en">
  <head>
    <slot name="head"></slot>
  </head>
  <body>
    <header><slot name="nav">Fallback nav</slot></header>
    <main><slot></slot></main>
  </body>
</html>
```

```html
<!-- index.html -->
<html lang="en">
  <head>
    <link rel="statica/layout" href="layouts/base.html" />
    <title>Home</title>
  </head>
  <body>
    <nav slot="nav"><a href="/">Home</a></nav>
    <h1>Hello</h1>
  </body>
</html>
```

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.

```html
<link rel="statica/font" href="@Google/?family=Outfit&display=swap" />
<script type="module" src="@static/app.js"></script>
<link rel="statica/fragment" href="@ui/post-card.html" id="post-card" />
```

`[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.

```html
<link rel="stylesheet" href="/app.css" />
<script type="module" src="/app.js"></script>
<img src="/logo.svg" alt="statica" />
```

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.

```html
<input type="statica/search" placeholder="Search" />
```

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.

```html
<form statica name="contact" method="post">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>
```

```toml
[forms]
enabled = true
provider = "formspree"
endpoint = "https://formspree.io/f/{id}"

[forms.ids]
contact = "your-form-id"
```

### i18n

Use a `[locale]` route segment and enable `[i18n]`. Catalogs live at `content/i18n/{locale}.json` by default.

```toml
[i18n]
enabled = true
locales = ["en", "pt"]
default = "en"
```

```html
<html lang="en" data-bind="{i18n}">
  <span data-t="${i18n.nav.home}">Home</span>
  <a href="/${i18n.locale}/">Home</a>
</html>
```

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.

```bash
statica build
```

## License

MIT

## Author

(c) 2026 Simão Nziaka