zest 0.7.3
dotnet tool install --global zest --version 0.7.3
dotnet new tool-manifest
dotnet tool install --local zest --version 0.7.3
#tool dotnet:?package=zest&version=0.7.3
nuke :add-package zest --version 0.7.3
Zest SSG
<p align="center"> <img src="zest.png" alt="Zest" width="128" height="128"> </p>
<h1 align="center">Zest SSG</h1> <p align="center"><em>Zest: Easy Static-site Toolkit</em></p> <p align="center"> <a href="LICENSE">License</a> · <a href="#quick-start">Quick Start</a> · <a href="#documentation">Docs</a> </p>
Zest is a hybrid F# + C# static site generator in which templates are real code, not strings. It is built on a single premise: your templating language and your host language should be one and the same.
Table of Contents
- Why Zest?
- Features
- Quick Start
- Writing Content
- Templates
- Project Layout
- Commands
- Architecture
- Documentation
- Building from Source
- Design Philosophy
- License
Why Zest?
Fast. Pages are compiled in parallel, cached aggressively, and served by a dev server with live reload. Large sites stay responsive because the build pipeline is embarrassingly parallel by design.
F# everywhere. Pages can be written as ordinary F# programs using a type-safe HTML DSL — loops, conditionals, functions, and data, with no template-language workarounds. Markdown remains available whenever prose is preferable to code.
No lock-in. The engine is template-engine agnostic. Nunjucks is the default, with a compatibility layer for 11ty-style languages (Liquid, HAML, Pug) and a standalone engine for native Handlebars/Mustache (
.hbs,.mustache). Output is plain, static HTML that can be hosted anywhere.Quiet by default. The bundled starter theme ships with no animation, no shadows, and no hover theatrics — typography and whitespace carry the page.
A single binary. One
dotnetCLI tool handlesinit,build,serve,clean, andpreview.
Features
Template as Code —
.zest.fsxfiles are real F# scripts executed at build time viadotnet fsi. Full F# is available: list comprehensions, pattern matching, string interpolation, and arbitrary computation.HTML DSL — Compose HTML declaratively, e.g.
render [ h1 []; p [] ].Inline Markdown — Write Markdown directly inside a
.zest.fsxpage using themdhelper, and mix it with the HTML DSL:md """# Title\n**bold**""".Markdown posts — Standard
.mdfiles with frontmatter support.ZCSS — A CSS superset with nesting, F#-style
letbindings, math expressions, color functions, and mixins — compiled to standard CSS.11ty-compatible templates — Full support for Nunjucks (
.njk), Liquid (.liquid), Handlebars (.hbs), Mustache (.mustache), HAML (.haml), and Pug (.pug). Nunjucks, Liquid, HAML, and Pug are auto-converted to the Nunjucks engine for filters, macros, template inheritance, and Zest API integration..hbsand.mustacherun on a dedicated Mustache/Handlebars engine that preserves their native syntax._init.zest.fsx— An optional initialization script (run before each build) for injecting dynamic data, loading JSON/TOML, and reading environment variables.TOML configuration — Zero-config defaults; customize via
_config.tomland_data/*.toml. No YAML.Live reload —
zest servewatches for changes and rebuilds automatically.Batch evaluation — Multiple F# page scripts are evaluated in a single FSI process for fast builds.
Incremental builds — File-change detection skips unchanged pages and assets.
Cross-platform — Builds for Windows x64, Linux x64/ARM64, and macOS ARM64.
Quick Start
# Scaffold a new project
zest init my-site
# Develop with live reload
cd my-site && zest serve --port 8080
# Build for production
zest build
# Preview the built site
zest preview
Writing Content
Markdown posts
+++
title = "Hello, world"
date = 2026-01-15
tags = ["zest", "fsharp"]
layout = "post"
+++
This is a blog post.
Pages as F# (.zest.fsx)
// @title About
// @layout default
render [
h1 [ text "About this site" ]
p [ text "Written in F#, rendered as HTML." ]
]
Example: a .zest.fsx page
// @title Hello World
// @layout default
// @description My first Zest page
let pageTitle = "Hello from F#"
let items = ["F#"; "Zest"; "SSG"]
render [
h1 [ text pageTitle ]
p [ text "This page is generated by real F# code at build time." ]
ul [ for i in items -> li [ text i ] ]
]
Example: inline Markdown inside a .zest.fsx page
The md helper renders a Markdown string to an HTML string, so prose and the F# HTML DSL can be blended within a single page. Like every other DSL builder, md returns a plain string.
// @title About
// @layout default
open Zest.Dsl
render [
divC "about" [
md """
# About
This page is a **native template** written in `.zest.fsx` (real F#), where
Markdown and the HTML DSL live side by side.
Learn more at the [Zest repository](https://github.com/zest-ssg/zest).
"""
]
]
Styles as ZCSS (.zcss)
// F#-style let bindings with math expressions
let primary = #3b82f6
let space1 = 0.25r
let space4 = space1 * 4 // 1rem
let primary-light = primary |> lighten(45%)
.tag [
color: $primary
background-color: $primary-light
padding-block: $space4
border-radius: 9999px
]
Compiles to:
.tag {
color: #3b82f6;
background-color: #adf4ff;
padding-block: 1rem;
border-radius: 9999px;
}
Data
_init.zest.fsx runs before every build and can inject global data:
addGlobal "socials" [
{| label = "GitHub"; url = "https://github.com/zest"; icon = "github" |}
]
Templates read it as {{ site.socials }}.
Templates
Layouts and partials are plain HTML processed by a template engine. Nunjucks is the default. Other 11ty-compatible languages are auto-converted to Nunjucks internally — except .hbs and .mustache, which run on a standalone Mustache/Handlebars engine, since the Nunjucks converter cannot fully express their syntax.
<!DOCTYPE html>
<html lang="{{ site.language }}">
<head>
<meta charset="utf-8">
<title>{{ site.title }}</title>
<link rel="stylesheet" href="/assets/css/main.css">
</head>
<body>
{{ include header.html }}
<main>
{{ content | safe }}
</main>
{{ include footer.html }}
</body>
</html>
Supported Nunjucks constructs include {{ include }}, {{ content }}, {% if %} / {% for %}, {% assign %}, filters (| t, | date, | readingTime), and i18n strings from _locales/*.toml.
Note: The
template_enginefield in_config.tomlis declarative only — it documents which engine the templates were written for. Layout routing is decided by file extension, so a project may freely mix Nunjucks, Handlebars, Liquid, and other templates.
Project Layout
.
├── zest.toml # CLI configuration
├── _config.toml # site metadata and build options
├── _init.zest.fsx # pre-build script (global data, hooks)
├── _data/ # global data (nav.toml, …)
├── _themes/<name>/ # self-contained themes
│ ├── _theme.toml # theme manifest
│ ├── _layouts/ # template layouts (.njk / .liquid / .hbs / …)
│ ├── _includes/ # partials
│ ├── _locales/ # i18n string tables
│ └── assets/ # styles (ZCSS), images, fonts
├── content/ # pages (.zest.fsx) and posts (.md)
└── _site/ # build output
Commands
| Command | Description |
|---|---|
zest init <name> |
Scaffold a new site from a starter. |
zest build |
Build the site into _site/. |
zest serve |
Start a dev server with live reload. |
zest preview |
Preview the built site. |
zest clean |
Remove build output. |
Architecture
| Project | Language | Responsibility |
|---|---|---|
| Zest.App | C# | CLI entry point, command routing, scaffolding, dev server, embedded starters. |
| Zest.Engine | F# | Build pipeline: content, layouts, ZCSS, data, feeds. |
| Zest.Dsl | F# | Type-safe HTML DSL for .zest.fsx pages. |
| Zest.Infra | C# | Configuration loading, file watching, logging, hashing, shared infrastructure. |
Documentation
File Types
| Extension | Purpose | Processing |
|---|---|---|
.zest.fsx |
F# script templates (F# + Markdown + HTML DSL) | Compiled via dotnet fsi |
.njk |
Nunjucks templates (filters, macros, inheritance, Zest API) | Rendered via NunjucksEngine |
.liquid |
Liquid templates (Jinja2 family, auto-converted) | Converted → NunjucksEngine |
.hbs |
Handlebars templates (native Mustache/Handlebars) | HbsEngine — standalone, no conversion |
.mustache |
Mustache templates (native Mustache/Handlebars) | HbsEngine — standalone, no conversion |
.haml |
HAML templates (auto-converted to HTML → Nunjucks) | HamlConverter → NunjucksEngine |
.pug |
Pug templates (auto-converted to HTML → Nunjucks) | PugConverter → NunjucksEngine |
.zcss |
ZCSS stylesheets (CSS superset) | Compiled to .css |
.md |
Standard Markdown | Rendered to HTML |
.toml |
Configuration and data (no YAML) | Parsed at build time |
ZCSS Reference
| Feature | Syntax / Example |
|---|---|
| Variables (SCSS) | $name: value; |
| Variables (F#) | let name = value |
| Math | let x = 0.25r * 4 |
| Color functions | lighten(#hex, %), darken(#hex, %), mix(a, b, %) |
| Pipe operator | value \|> fn(args) → fn(value, args) |
| Unit shorthands | r → rem, p → % |
| Property shorthands | py → padding-block, mx → margin-inline, bgc → background-color |
| Nesting | Indent or brace mode |
| Mixins | @mixin, @include |
| Loops | @each, @for |
| Conditionals | @if, @else |
| Built-in modules | @use "zest:utilities", @use "zest:palette", etc. |
Template Language Annotation
The template_engine (top-level) or [template] engine field in _config.toml is a pure annotation describing the site's primary template language. It does not affect the build; layout routing is decided by file extension only.
| Config value | Labels (primary template language) |
|---|---|
native |
.zest.fsx — F# script templates |
nunjucks |
Nunjucks — .njk / .html layouts |
liquid |
Liquid — .liquid layouts |
| (any value) | Pure label; no effect on the build |
Layouts are routed by file extension: .hbs and .mustache are rendered by the standalone HbsEngine; all other non-.zest.fsx extensions (.html, .njk, .liquid, .haml, .pug) go through the Nunjucks compatibility layer; and .zest.fsx layouts are always evaluated as F# scripts.
HTML DSL Reference
// Elements
h1 [ text "Title" ]
p [ text "Paragraph" ]
a [ href "https://example.com"; text "Link" ]
// Attributes
div [ class' "container"; id "main" ] [ ... ]
// CSS class shortcuts
divC "card" [ p [ text "Content" ] ] // <div class="card">
spanC "badge" [ text "New" ] // <span class="badge">
// List comprehensions
ul [ for item in items -> li [ text item ] ]
// Conditionals
if condition then
p [ text "Yes" ]
else
p [ text "No" ]
_init.zest.fsx API
| Function | Purpose |
|---|---|
addGlobal key value |
Inject a key-value pair into global data. |
loadJson path |
Parse a JSON file. |
loadToml path |
Parse a TOML file. |
loadEnv key |
Read an environment variable. |
console_log msg |
Emit debug output to stderr. |
exec cmd args |
Run a shell command. |
Building from Source
git clone https://github.com/zest-ssg/zest
cd zest
dotnet build Zest.sln
# Publish for your platform
dotnet publish src/Zest.App/Zest.App.csproj -c Release -r win-x64 --self-contained false
# Linux: -r linux-x64
# macOS: -r osx-arm64
Design Philosophy
- Content is code, code is content. The F# DSL and the template engine share a single data model, so nothing is lost at the boundary.
- No magic. Every transformation is a plain pipeline stage readable in the source. No hidden runtime and no implied dependencies.
- Speed is a feature. Parallel compilation, minimal allocations, and caching are part of the core design, not an afterthought.
- The output is the deliverable. Static HTML, no JavaScript required, host it anywhere.
Zest is not a general-purpose static site generator. It is a specific answer to specific constraints: F# as the template, TOML as the contract, no Node.js, and no YAML.
License
Apache License 2.0. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.3 | 48 | 9/12/2026 |
| 0.7.2 | 43 | 9/12/2026 |
| 0.7.1 | 48 | 9/11/2026 |
| 0.7.0 | 44 | 9/11/2026 |
| 0.6.1 | 156 | 8/8/2026 |
| 0.6.0 | 126 | 8/8/2026 |
| 0.5.2 | 129 | 8/7/2026 |
| 0.5.0 | 139 | 8/2/2026 |
| 0.4.0 | 143 | 8/2/2026 |
| 0.3.2 | 127 | 7/26/2026 |
| 0.3.1 | 144 | 7/19/2026 |
| 0.3.0 | 108 | 7/19/2026 |
| 0.2.1 | 141 | 7/18/2026 |
| 0.2.0 | 134 | 7/17/2026 |
| 0.1.0 | 150 | 7/17/2026 |