A lightweight Rust-based static site generator powering the site/ output directory.
It converts Markdown posts into HTML pages with SEO-friendly metadata, pagination, category pages, and an auto-generated sitemap.
- Markdown to HTML conversion (Comrak, smart punctuation, fenced code class injection)
- Category pages with automatic pagination (window & ellipsis logic)
- Synthetic Home navigation item with configurable display name (
home_page_name) - Home page population via
home: trueflag (independent of synthetic category entry) - Per-post categories with inline link rendering (footer of post pages)
- Unified pagination component with ARIA & rel attributes (
prev,next,first,last) - SEO metadata injection (Open Graph, Twitter, canonical, robots)
- Structured data (JSON-LD) injected (WebSite on home, sitePosting on posts) & stripped if empty
- Dynamic or overridden language attribute (
<html lang>fallback fromog_locale) - Automatic sitemap generation (
sitemap.xml) - Accessibility: list item images get descriptive
altattributes - Canonical URL validation (empty canonical panics early)
- Robots meta emitted only when deviating from defaults (
noindex,followon paginated category pages) - Newest-first sorting by ISO date for home & category lists
This project provides a sample directory containing example configuration, templates, and content. To try the sample site or use the generator, you must:
- Rename the
sampledirectory toresourcesin your project root:mv sample resources
- Or, create your own
resourcesdirectory with the following structure:resources/ params.yaml # global site params (required) posts.yaml # list of posts (required) content/ # markdown source (each post: <id>.md) assets/img/ # images used by posts + logo/no-image fallback templates/ # HTML templates (required set below)
All template filenames are currently fixed:
resources/templates/category_item.html
resources/templates/category.html
resources/templates/home.html
resources/templates/meny.html # menu navigation item list wrapper
resources/templates/category_link.html # category anchor inside a post footer
resources/templates/pagination_item.html # one pagination list item (number / prev / next / ellipsis)
resources/templates/pagination.html # container wrapping pagination items
resources/templates/post.html # referenced per post via posts.yaml "template" field
Copy these from the sample/templates directory or create your own with the documented placeholders (see below).
Example starter:
items_per_page: 5
pagination_window: 2
language: en
og_locale: en_US
site_base_url: https://example.com
site_name: Example Site
site_description: A demonstration static site.
home_page_name: Home
Example (one post):
- id: hello-world
title: "Hello World"
description: "First post"
template: post.html
icon: "" # empty => uses no-image.png fallback
categories:
general: "General"
home: true
date: "2025-01-01"
Create matching markdown: resources/content/hello-world.md:
# Hello World
This is my first post.
Add optional images into resources/content/assets/img/ (include logo.png and no-image.png if you rely on fallbacks).
From the project root:
./static-site-gen
Optional logging verbosity (uses env_logger):
RUST_LOG=info ./static-site-gen # default informational messages
RUST_LOG=debug ./static-site-gen # more granular debug output if added later
On success you'll see log lines ending with Done. and a populated site/ directory:
site/
index.html
<post-slug>.html
<category>.html
<category>/<n>/ (paginated directories, trailing slash form)
sitemap.xml
assets/ (copied from src/md/assets)
You can host site/ with any static server (Nginx, Netlify, GitHub Pages). Ensure site_base_url in params.yaml matches the deployed origin; it is baked into canonical, OG image URLs, and sitemap entries.
Common tokens (replace automatically):
{{page_title}} {{page_description}} {{title}} {{description}}
{{canonical}} {{robots}} {{og_type}} {{og_locale}} {{og_image}} {{site_name}} {{structured_data}} {{lang}}
{{menu}} {{content}} {{path}}
{{categories}} {{date}} {{page}} {{icon}}
Avoid conflicting substring names (e.g. do not put title inside page_title unintentionally).
Repeat ./static-site-gen after editing markdown or YAML. The entire site/ folder is deleted first; keep anything persistent (like deployment scripts) outside site/.
- Missing markdown (
src/md/<id>.md) or missing referenced post template: post skipped (warning logged, build continues). - Empty canonical (derived from
site_base_url+ path) would trigger a panic (guard against emptysite_base_url). - Serious I/O or YAML parse errors: process exits non-zero and prints a message.
0: Successful generation- Non-zero: Fatal error (inspect printed message)
- Change pagination size via
items_per_page. - Adjust visible pagination range with
pagination_window. - Override
<html lang>withlanguage; omit to derive from first part ofog_locale. - Provide post-specific icons (images) in
src/md/assets/img/and reference viaiconfield; empty string usesno-image.png. - Replace
logo.pngfor site-level OG images.
mkdir -p my-site/src/{md/assets/img,html}
cd my-site
# (Download binary here)
chmod +x static-site-gen
cat > src/params.yaml <<'EOF'
items_per_page: 5
pagination_window: 2
og_locale: en_US
site_base_url: https://example.com
site_name: Example Site
site_description: Example description
home_page_name: Home
EOF
cat > src/posts.yaml <<'EOF'
- id: hello-world
title: "Hello World"
description: "First post"
template: post.html
icon: ""
categories:
general: "General"
home: true
date: "2025-01-01"
EOF
cat > src/md/hello-world.md <<'EOF'
# Hello World
This is my first post.
EOF
# Copy or author required templates into src/html/ ...
./static-site-gen
| Symptom | Cause | Fix |
|---|---|---|
| Post missing in output | Missing markdown or template file | Ensure src/md/<id>.md and src/html/<template> exist |
| Images not showing | Wrong filename or missing in assets/img |
Place correct file under src/md/assets/img |
| Wrong canonical base | Incorrect site_base_url in params |
Update params.yaml and rerun |
| Sitemap absent | Fatal earlier error stopped build | Re-run with RUST_LOG=info and inspect output |
| Language incorrect | language omitted & og_locale prefix unexpected |
Set explicit language: in params |
CLI flags for custom source/output paths are not yet available (paths are fixed). For now, keep the described structure. Roadmap items (RSS, drafts, incremental builds) will extend this section as they land.
Markdown (.md) + posts.yaml + params.yaml
| parse & load
v
Aggregate categories (insert synthetic Home, sort by title)
v
Render pages:
- Posts (markdown -> HTML, template fill, SEO cleanup)
- Category pages (paginate, template fill, canonical/robots logic)
- Home page (filter posts with home: true)
v
Generate sitemap (scan output .html -> canonical URLs)
v
Copy assets (CLI step AFTER generation to avoid deletion)
⚠ The site_path directory is fully cleared before writing new output. Do not place persistent artifacts there.
Current pattern (mixed by design):
- Home:
https://example.com/(trailing slash) - Posts:
https://example.com/<slug>.html - First category page:
https://example.com/<category>.html - Paginated category pages:
https://example.com/<category>/<n>/(trailing slash directory form) This is documented for transparency; you may standardize later (e.g. all trailing slash or all.html).
The Settings struct includes:
md_path: Source Markdown directory (<id>.mdper post)site_path: Output directory (site/, cleared before generation)html_path: Directory containing HTML templatessite_params: Runtime & SEO params (see below)category_item_template,category_template,home_template,menu_item_template,category_link_template,pagination_item_template,pagination_template: Template file paths (string paths to HTML files)
Example:
items_per_page: 2 # category pagination size
pagination_window: 2 # number of page numbers shown on EACH side of current page
language: en # optional; if omitted derived from og_locale (e.g. en_US -> en)
og_locale: en_US
site_base_url: https://example.com
site_name: 8sun site
site_description: An example site for demonstration purposes
home_page_name: Home # label for synthetic home menu item (slug fixed: index)
Field notes:
items_per_page: category pagination sizepagination_window: symmetric window of adjacent page indices (total visible pages grows with current location)language: direct<html lang>override, else derived fromog_localeog_locale: Open Graph locale & fallback source for languagehome_page_name: display name for navigation link to home page (index.html)
Required fields per post id (also expect a <id>.md file in md_path):
- id: example-post
title: "Example Post"
description: "Short description"
template: post.html # must exist under html_path
icon: "optional_icon.png" # leave empty string for fallback image (no-image.png)
categories:
philosophy: "Philosophy" # slug: display name (multiple allowed)
home: true # includes on home page list
date: "2025-01-01" # ISO format (YYYY-MM-DD) used for lexicographic newest-first sorting
Missing markdown or template => post skipped with a logged warning (build continues).
- Home posts: filter
home: true, then sort descending bydate(ISO 8601 string compare). - Category pages: posts in that category sorted descending by
date. - Categories: synthetic
indexfirst, remaining categories sorted lexicographically by title.
| Aspect | Behavior |
|---|---|
| Canonical | Always emitted; generation panics if empty |
| Robots | Omitted for indexable pages (home, posts, first category page); noindex,follow on paginated category pages |
| Open Graph | og:title, og:description, og:type, og:site_name, og:locale, og:image |
summary_large_image card (via OG fields) |
|
| Structured Data | WebSite (home), sitePosting (posts); empty script node removed |
| Language | <html lang> from language or derived from og_locale prefix |
| Sitemap | site/sitemap.xml enumerates canonical URLs |
Placeholders are simple {{key}} tokens. Avoid overlapping names (e.g. title inside page_title). Provided pairs include aliases for convenience.
Core placeholders:
- Page meta:
{{page_title}},{{page_description}},{{title}}(alias),{{description}}(alias) - SEO:
{{canonical}},{{robots}},{{og_type}},{{og_locale}},{{og_image}},{{site_name}},{{structured_data}},{{lang}} - Layout:
{{menu}},{{content}},{{path}} - Listing / post specific:
{{categories}},{{date}},{{page}}(link stem for items),{{icon}}
Template roles:
category_item_template: per-post summary item (usespage,icon,title,description,date)category_template: wraps combined list + pagination blockhome_template: wraps home listmenu_item_template: per-category navigation link (including synthetic Home)category_link_template: category anchor inside a post page footerpagination_item_template+pagination_template: pagination UI- Post page template (e.g.
post.html): full post layout with categories/date injection
- Home: JSON-LD WebSite object
- Post: JSON-LD sitePosting object Empty payload => script removed during cleanup.
YAML usage:
language: fr
og_locale: fr_FR
Produces <html lang="fr"> regardless of other inference rules.
- Source images & static files expected under
src/md/assets/. - After page generation the CLI copies to
site/assets/(order intentional: generation wipes output first). - Fallback filenames:
logo.png(site logo for home/category OG image),no-image.png(used when posticonempty). - Image URLs constructed as:
<site_base_url>/assets/img/<file>.
- Missing markdown file for a post id: post skipped (warning logged).
- Missing template file referenced by a post: post skipped (warning logged).
- These are non-fatal; overall build succeeds unless a critical I/O error occurs.
pagination_window= number of adjacent page numbers displayed on each side of current page.- Always includes first & last page numbers.
- Ellipsis item inserted where numeric gaps occur.
- Rel attributes (
prev,next,first,last) applied when links are active for crawlers & accessibility.
Adding a new metadata field:
- Add to
MetaPageand propagate increate_basic_pageplaceholder pairs. - Add
{{field}}to relevant templates. - Populate when constructing
MetaPagein generation code.
Adding a new page type:
- Create template under
html_path. - Implement helper akin to existing
create_*_pagefunctions. - Construct
MetaPageand invoke helper during generation.
Customizing pagination appearance:
- Edit
pagination_item_template/pagination_template(markup & classes).
Custom category ordering:
- Modify
Category::from_postsor add a custom ordering layer (future roadmap includes config-based ordering).
- Place Markdown at
src/md/<slug>.md. - Add entry to
src/posts.yaml. - (Optional) Add image:
src/md/assets/img/<icon>. - Run
cargo run.
Tests assert:
- Pagination (window, ellipsis, prev/next/first/last states)
- Robots meta omission & paginated
noindex,follow - Language override & fallback derivation
- Canonical presence (panic if empty)
- Structured data retention vs cleanup
- Sitemap URL discovery logic
- Category link isolation vs menu
- Markdown rendering (headings, emphasis, fenced code language)
- Path prefix correctness for nested pages
Near Term examples: RSS generation, draft support, selective rebuild flags. Medium Term: Incremental builds, tags taxonomy, pluggable templating. Stretch: Multilingual, image optimization, search index, live preview server.
MIT License. See LICENSE file.