fssg

Tiny, dependency-free static site generator

Convert Markdown and html into a deployable site with just pure POSIX shell.

Why Choose fssg?

Run Anywhere

If you have a POSIX shell and an awk implementation, fssg runs. No Node, no package managers — just one script you can drop into a project.

.
├── fssg
└── src
    ├── index.md
    └── template.html

Fast & Parallel

Rendering is handled with optimized AWK routines; pages are processed in parallel child processes. The default concurrency is 100 so large sites build quickly.

./fssg -j 100

Simple, Focused Tooling

Built-in watcher, optional dev server and autoreload script give a modern workflow without adding dependencies. Server selection prefers a local ./mongoose binary, then system mongoose, then python3 / python. You can override the exact server command.

./fssg -j 100 -s -w -o

Quick Start Guide

1

Install

Download the single script or clone the repo if you want the optional mongoose dev-server binaries.

Option A: script only

wget https://raw.githubusercontent.com/xlc-dev/fssg/main/fssg
chmod +x fssg

Option B: clone (and copy what you need)

git clone https://github.com/xlc-dev/fssg.git
mkdir mysite && cd mysite
cp ../fssg/fssg .
cp -r ../fssg/mongoose .

fssg requires an awk available on PATH. To use a specific awk, set FSSG_AWK (for example FSSG_AWK=gawk).

2

Create Content

Put your template at src/template.html and your pages under src/ as .md or .html. fssg injects processed page content into the master template at {{content}}.

Example: src/template.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{{title}}</title>
  <link rel="stylesheet" href="{{BASE_URL}}/static/css/styles.css">
</head>
<body>
  {{content}}
</body>
</html>

Example: src/index.md

# Hello, World!

This is my first page built with fssg.
3

Build & Preview

Build the site and (optionally) start the dev server and watcher:

./fssg -s -w -o

Default dev-server port is 8000 (override via FSSG_SERVER_PORT). When watching or serving fssg writes dist/.reload and injects a small autoreload client that polls /.reload to trigger reloads.

Features & Documentation

  • CLI
  • Directory Structure
  • Templating
  • Content & Assets
  • Conditionals
  • Server & Watch

Command-Line Interface

./fssg [options]
  • -h, --help: Show help and exit.
  • -V, --version: Print the script's version and exit.
  • -q, --quiet: Suppress informational output (errors still shown).
  • -v, --verbose: Enable verbose debug logging.
  • -n, --nocolor: Disable ANSI color sequences in messages.
  • -w, --watch: Watch the src/ tree and rebuild on changes.
  • -s, --serve: Start a dev server for dist/. fssg prefers a local ./mongoose binary, then system mongoose, then python3, then python.
  • -o, --open: Open the browser after starting the server (use with -s).
  • -m, --minify: Disable the HTML minifier (minification is enabled by default). This flag turns minification off for debugging.
  • -j <num>: Number of parallel build jobs (default: 100).

Environment variables

  • FSSG_BASE_URL: Base URL value that fssg substitutes for {{BASE_URL}} inside templates and includes at build time. (Default: empty string, which yields root-relative paths.)
  • FSSG_SERVER_CMD: Override the dev-server command used by -s.
  • FSSG_SERVER_PORT: Port used by the dev server (default: 8000).
  • FSSG_AWK / FSSG_AWK_OPTS: Customize the awk program and options.

Project Structure

.
├── fssg
├── mongoose/
│   ├── mongoose_linux
│   ├── mongoose_macos
│   └── mongoose_windows.exe
└── src/
    ├── includes/
    ├── static/
    ├── template.html
    ├── index.md
    └── about.html
  • src/: Source files (Markdown and HTML).
  • mongoose/: Optional dev-server executables.
  • src/template.html: Master layout. Must include {{content}}.
  • src/includes/: Reusable snippets (use {{include: ...}}).
  • src/static/: Copied to dist/static/ unchanged.
  • dist/: Generated site output (created by fssg).

Templating & Title Rules

Title precedence

The title is chosen by these rules (highest → lowest):

  1. An explicit title directive inside the source file: {{title: My Page}} (this is removed from the rendered content and becomes the page title).
  2. If none present, a title is generated from the filename: hyphens become spaces and each word is capitalized.

Examples:

  • src/my-first-post.md → "My First Post"
  • src/2025-10-06-my-post.md → "2025 10 06 My Post" (the date prefix is not stripped)
  • src/my_first_post.md → "My_first_post" (underscores kept)

Implementation caveats:

  • fssg searches only the primary source file for the title directive. A title inside an included file will not set the page title.
  • The extracted title is taken as-is by the code that finds it (no extra trimming), so keep the directive line clean.

Includes

Simple include from src/includes/:

{{include: footer.html}}

Include-blocks with parameters

Pass named parameters and a content block to an include. Inside the include file use {{content}} to inject the provided block. If you pass markdown="true" (or markdown=1), the block is converted to HTML before injection.

{{include-block: alert.html type="warning" alert_title="Attention" markdown="true"}}
This is **Markdown** content inside the block.
{{endinclude}}

If an include-block with markdown="true" produces a full HTML document (contains a DOCTYPE or an <html> tag), fssg treats the page as pre-rendered HTML and skips the page-level Markdown pass.

Content Processing & Assets

Markdown support

fssg includes an AWK-based Markdown parser with support for:

  • Headings, paragraphs, and raw HTML passthrough
  • Blockquotes, fenced code blocks and inline code
  • Lists (unordered/ordered), links, images
  • Bold/italic/strikethrough, horizontal rules
  • Tables (pipe-style)

Static assets

Everything under src/static/ is copied to dist/static/ unchanged. Use {{BASE_URL}} in templates to build correct deploy-time prefixes — in documentation examples encode the braces as {{BASE_URL}} so they appear literally in the generated docs.

Style & script hoisting

fssg collects <style>...</style> blocks and emits them immediately before the closing </head>, and collects <script>...</script> blocks and emits them immediately before the closing </body>. The discovery order is preserved. fssg does not deduplicate identical blocks.

Minification

Minification is enabled by default. The minifier:

  • Removes HTML comments and extra whitespace
  • Cleans attribute whitespace inside tags
  • Preserves <pre> blocks verbatim
  • Strips CSS and JS comments inside hoisted <style> and <script> blocks

Use -m to disable minification while debugging.

Auto-reload

When using the watcher or serving, fssg writes a timestamp into dist/.reload and injects a small client script that polls /.reload. If you host your built site under a subpath, ensure the dev server exposes /.reload, or override the server with FSSG_SERVER_CMD.

Conditionals

fssg supports simple conditional blocks using exact string equality. Use IF_PAGE / ELIF_PAGE / ELSE_PAGE / ENDIF_PAGE to test the output path (relative to dist/), or IF_EXT / ELIF_EXT / ELSE_EXT / ENDIF_EXT to test the source file extension.

By output page path

{{IF_PAGE: index.html}}
  <p>Only for the homepage.</p>
{{ELIF_PAGE: about.html}}
  <p>Only for the about page.</p>
{{ELSE_PAGE}}
  <p>For all other pages.</p>
{{ENDIF_PAGE}}

By source extension

{{IF_EXT: md}}
  <p>This content was originally Markdown.</p>
{{ELIF_EXT: html}}
  <p>This content was originally HTML.</p>
{{ELSE_EXT}}
  <p>Other source formats.</p>
{{ENDIF_EXT}}

Conditions are evaluated during template processing. They match exact strings, no globs or regexes and support nesting.

Server, Watcher & Overrides

Dev server selection

When -s is used to serve:

  1. If FSSG_SERVER_CMD is set, fssg runs that command verbatim.
  2. If a local ./mongoose directory exists, fssg looks for a platform-specific executable and runs it with -d dist/ -v 0.
  3. If mongoose is in PATH it will be used.
  4. Otherwise fssg falls back to python3 -m http.server <port> or python -m SimpleHTTPServer <port>.

Watcher

The watcher computes a checksum snapshot of all files in src/ and triggers a rebuild when the snapshot changes. It is portable and doesn't require platform-specific file-watching APIs.

Open browser

Use -o together with -s to open the default browser automatically (open on macOS, xdg-open on Linux, or start on Windows).