Creestal

Creestal is a static site generator written in Crystal.

It builds Markdown pages into HTML using Crinja layouts and partials, can serve the generated site locally, and supports incremental rebuilds while watching for changes.

Installation

Prerequisite:

Build from source:

shards install
shards build

The binary is written to:

bin/creestal

Optional local install:

cp bin/creestal /usr/local/bin/creestal

Quick Start

Create a site:

creestal new my-site
cd my-site

Build it:

creestal build

Serve it locally:

creestal serve --watch

Default URL:

http://localhost:3000

Commands

creestal new [name]

Create a new scaffolded site.

Flags:

creestal build/b

Run a full build from the configured source directory to the configured output directory.

Flags:

Behavior:

creestal serve/s/srv

Serve the configured output directory.

Flags:

Behavior:

Project Structure

Scaffolded projects use this structure:

my-site/
	config.yml
	src/
		pages/
		layouts/
		partials/
		assets/
	out/

Directories:

Configuration

Creestal reads YAML from config.yml.

Example:

site: "My Site"
home: "index"
source: "src"
output: "out"
default_layout: "base"
watcher_interval_ms: 500

Fields:

Defaults:

Pages

Pages are Markdown files in src/pages/ with YAML front matter.

Example:

---
title: Hello
layout: base
date: 2026-05-04
tags: [intro, docs]
draft: false
---

# Hello

Supported front matter fields:

Routing/output examples:

Path normalization:

Draft behavior:

Layouts and Partials

Layouts are regular HTML files rendered with Crinja.

Partials are included with standard Crinja include syntax:

{% include "header.html" %}

Creestal loads includes from src/partials/ first, then from the source root.

Included partials receive the same render context as the parent layout.

Render Context

Each render receives three top-level values:

page

Available fields:

Most commonly used:

<title>{{ page.title }}</title>
<main>{{ page.content }}</main>

site

Available collections:

Each page node in these collections contains:

Notes:

Example:

{% for post in site.posts %}
	<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}

active_page

active_page is the current page path without extension.

It is exposed as a top-level render value, not as page.active_page.

Examples:

Creestal also exposes a helper:

{{ is_active("about", "active") }}

is_active(current, value) returns value when current == active_page, otherwise it returns an empty string.

Example in markup:

<a class='{{ is_active("about", "active") }}' href="/about">About</a>

Dependency Tracking

Creestal tracks layout and partial dependencies so incremental rebuilds only touch affected pages.

Currently:

Development

Run the full test suite:

crystal spec

Run targeted suites:

crystal spec spec/core
crystal spec spec/commands
crystal spec spec/integration
crystal spec spec/server

Build the binary:

shards build

Status

Creestal is under active development.

License

MIT