rs-web - Static Site Generator
A fast, opinionated static site generator built in Rust with support for:
- Markdown processing with syntax highlighting, external link handling
- Content encryption (full post or partial
:::encryptedblocks) - HTML content files with Tera templating support
- Link graph with backlinks and visualization (Obsidian-style)
- RSS feed generation with section filtering
- Parallel processing for fast builds
Installation
Or with Nix:
Quick Start
# Build the site
# Build to custom output directory
# Watch for changes and rebuild incrementally
Logging
Control log verbosity with --debug, --log-level, or the RS_WEB_LOG_LEVEL environment variable.
# Enable debug logging (shorthand)
# Set specific log level (trace, debug, info, warning, error)
# Use environment variable
RS_WEB_LOG_LEVEL=debug
Priority order: --debug > --log-level > RS_WEB_LOG_LEVEL > default (warning)
Configuration
Configure via config.lua (recommended) or config.toml:
Lua Configuration (config.lua)
Lua configuration provides more power with computed data, dynamic pages, and build hooks:
return
Lua Sandbox
By default, Lua file operations are sandboxed to the project directory (where config.lua is located). This prevents accidental or malicious access to files outside your project.
return
When sandbox is enabled:
- File operations (
read_file,write_file,load_json, etc.) only work within the project directory - Attempting to access files outside returns an error with a helpful message
- Relative paths are resolved from the project root
Lua API Functions
Available in config.lua:
| Function | Description |
|---|---|
read_file(path) |
Read file contents, returns nil if not found |
write_file(path, content) |
Write content to file, returns true/false |
file_exists(path) |
Check if file exists |
list_files(path, pattern?) |
List files matching glob pattern |
list_dirs(path) |
List subdirectories |
load_json(path) |
Load and parse JSON file |
env(name) |
Get environment variable |
print(...) |
Log output to build log |
Note: All file operations respect the sandbox setting. Paths can be relative (resolved from project root) or absolute.
Async/Await Helpers
Coroutine-based cooperative multitasking with a cleaner API:
-- Create and run tasks
local task1 = async.
-- Run single task to completion
local result = async.
-- Run multiple tasks (interleaved execution)
local results = async.
-- Race: return first completed
local winner, index = async.
Parallel Processing
True parallel execution using Rust's rayon thread pool:
-- Load multiple JSON files in parallel (I/O parallelism)
local configs = parallel.
-- Read multiple files in parallel
local contents = parallel.
-- Check multiple files exist in parallel
local exists = parallel.
-- Functional helpers (sequential but convenient)
local doubled = parallel.
local evens = parallel.
local sum = parallel.
TOML Configuration (config.toml)
Required Settings
[]
= "My Site" # Site title
= "Site description" # Site description
= "https://example.com" # Base URL (no trailing slash)
= "Your Name" # Author name
[]
= "@username" # Optional: Twitter handle
= "/static/og.png" # Optional: Default OG image
[]
= "dist" # Output directory
= true # Default: true
[]
= 85.0 # WebP quality (default: 85.0)
= 1.0 # Image scale (default: 1.0)
Optional Settings (have defaults)
[]
= "content" # Content directory (default: "content")
= "styles" # Styles directory (default: "styles")
= "static" # Static files (default: "static")
= "templates" # Templates (default: "templates")
= "index.md" # Home page file (default: "index.md")
= ["drafts", "^temp.*"] # Regex patterns to exclude files/dirs (default: [])
= true # Exclude README.md, LICENSE.md, etc. (default: true)
= true # Respect .gitignore (default: true)
[]
= ["John Doe", "Jane Doe"] # Names to highlight (default: [])
= "me" # CSS class for highlights (default: "me")
[]
= "post.html" # Section -> template mapping
= "project.html" # (default: uses {section}.html or post.html)
[]
= "/:year/:month/:slug/" # Section -> URL pattern
= "/:slug/" # Placeholders: :year :month :day :slug :title :section
[]
= "pass show site" # Command to get password (optional)
= "secret" # Raw password (optional, less secure)
# Priority: SITE_PASSWORD env > command > password
[]
= true # Enable graph generation (default: true)
= "graph.html" # Graph page template (default: "graph.html")
= "graph" # URL path (default: "graph" -> /graph/)
[]
= true # Enable RSS generation (default: true)
= "rss.xml" # Output filename (default: "rss.xml")
= ["blog"] # Sections to include (default: [] = all)
= 20 # Max items (default: 20)
= false # Exclude posts with :::encrypted (default: false)
Root Pages
Markdown files at the content root (besides the home page) are processed as standalone pages. For example, 404.md becomes 404.html:
---
title: "404 - Page Not Found"
template: "error.html"
---
# Page Not Found
The page you're looking for doesn't exist.
Default excluded files (disable with exclude_defaults = false):
- README.md, LICENSE.md, CHANGELOG.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md
- Hidden files (starting with
.)
Frontmatter
Post frontmatter options (YAML or TOML):
---
title: "Post Title" # Required
description: "Description" # Optional
date: # Optional (YAML date or string)
tags: # Optional
draft: false # Optional (default: false, excluded from build)
image: "/static/post.png" # Optional: OG image
template: "custom.html" # Optional: Override template
slug: "custom-slug" # Optional: Override URL slug
permalink: "/custom/url/" # Optional: Full URL override
encrypted: false # Optional: Encrypt entire post
password: "post-secret" # Optional: Post-specific password
---
Content Types
Markdown Files (.md)
Standard markdown files processed through the markdown pipeline.
HTML Files (.html)
HTML files with Tera templating support. Can use extends, includes, and all Tera features:
+++
title = "Custom Page"
date = 2024-01-15
+++
{% extends "base.html" %}
{% block content %}
{{ post.title }}
By {{ site.author }}
{% endblock %}
Partial Encryption
Markdown
Use :::encrypted blocks for partial content encryption:
Public content here.
:::encrypted
This content is encrypted with the global/post password.
:::
:::encrypted password="custom"
This block has its own password.
:::
HTML
Use <encrypted> tags in HTML files:
Public content here.
This content is encrypted.
This block has its own password.
Template Variables
Home Template (home.html)
site- Site config (title, description, base_url, author)page- Page info (title, description, url, image)sections- All sections with posts (sections.blog.posts)content- Rendered markdown content
Post Template (post.html)
site- Site configpost- Post info (title, url, date, tags, reading_time, etc.)page- Page info for head.html compatibilitycontent- Rendered markdown contentbacklinks- Posts linking to this post (url, title, section)graph- Local graph data (nodes, edges) for visualization
Graph Template (graph.html)
site- Site configpage- Page infograph- Full graph data (nodes, edges)
License
MIT License. See LICENSE for details.