ZeroSite
Source

ZeroSite

ZeroSite is an independent Go command that renders repository Markdown into a static site. It has no dependency on the project's implementation, its root module, or a neighboring checkout. Repository-specific policy lives in a JSON configuration.

Tutorial · Configuration guide · Go extension guide · Cookbook · Configuration reference

Build and preview

Requires Go 1.27.1 or later. From the ZeroSite repository root:

sh site/build.sh
sh site/build.sh -check
sh site/build.sh -serve localhost:8080

The build downloads the Go dependencies on first use, then renders local inputs. Preview builds once; rebuild after editing source or configuration. To deploy under a URL prefix, use -base-path /project/; preview serves that same prefix. All generated page, navigation and asset links are relative, including search. Generated pages can also be opened through file://.

The reusable command can build an unrelated repository:

go build -o /tmp/zerosite .
/tmp/zerosite -repo /path/to/project -config /path/to/site.json -out /tmp/project-site

-config and -out are resolved against the command's working directory. Include patterns, custom template paths and staticDir are relative to -repo. -check performs parsing, routing, rendering and link validation without writing output. -drafts includes draft pages locally and adds noindex metadata.

Configuration

Start from site/config.json. Unknown keys fail the build to catch typos.

Key Behavior
title, description, lang Site identity, fallback page description, HTML language
repository, branch Optional repository URL and branch for source links; blank disables them
siteURL Optional HTTP(S) origin such as https://docs.example.com; enables canonical and Open Graph page URLs, sitemap and robots file
sitemap.lastmod git adds sitemap <lastmod> dates from commit history; default empty omits them
basePath Deployment prefix, default /; use /project/ for subpath hosting
include Repository-relative Markdown globs
exclude Globs excluded from pages and copied assets; wins over include
assets Explicit repository assets copied at their repository-relative path
prettyURLs topic.md becomes topic/index.html when true, topic.html when false
drafts Include draft pages; default false
search, toc Global search and per-page TOC defaults
searchHeadings Add heading fragments to search results; default false
pager Previous/next scope: section (default), group, site, or none
sections Nested navigation groups with globally unique titles, match, children, optional index and first
recordIndexes Virtual Markdown indexes for numbered design or optimization records
markdown Raw HTML, alerts, heading links/IDs and alignment behavior
tocDepth, searchIndex, directoryLinks, repositoryLinks Go generator settings for TOC/search output and source links
defaults Ordered match globs and values metadata; later matches override earlier values
redirects Old route to published route mapping, e.g. /old.html to /guide/
template Optional repository-relative Go HTML template, replacing the embedded layout
staticDir Optional public directory copied directly into the output root
styles, scripts Additional local output-relative assets loaded after the bundled CSS and JS

Globs support *, ?, character classes, and ** as a whole path segment. ** matches zero or more directories. Braces, negated pattern lists and platform-specific separators are not supported. Use separate patterns instead. The generator does not follow selected input symlinks.

Default inputs are README.md, docs/**/*.md, and spec/**/*.md. Authoring TEMPLATE.md and 000-template.md files, underscore-prefixed Markdown files, and underscore-prefixed directories are excluded by the template configuration. The engine does not hardcode any documentation section or project name.

For example, record indexes publish a generated list beside design records, and nested sections organize guides under a common navigation group:

{
  "recordIndexes": [
    {
      "source": "docs/design/README.md",
      "directory": "docs/design",
      "title": "Design Records",
      "introduction": "Accepted and proposed design decisions.",
      "columns": [
        { "title": "Record", "field": "Title" },
        { "title": "Status", "field": "Status" },
        { "title": "Date", "field": "Date" }
      ],
      "required": ["Status", "Date"],
      "templateText": "See the record files for the full rationale."
    }
  ],
  "sections": [
    {
      "title": "Documentation",
      "match": ["README.md"],
      "children": [
        {
          "title": "Guides",
          "match": ["docs/guide/**"],
          "index": "docs/guide/README.md",
          "first": ["docs/guide/getting-started.md"]
        },
        { "title": "Design", "match": ["docs/design/**"] }
      ]
    }
  ],
  "pager": "group",
  "markdown": {
    "rawHTML": false,
    "alerts": true,
    "headingLinks": true,
    "math": true,
    "headingIDs": "unicode",
    "alignAttributes": true
  },
  "searchHeadings": true
}

Each recordIndexes entry creates a virtual Markdown source at source; its front matter and matching defaults are handled like those of a file, and its route, links and fragments resolve normally. The source file is never created or overwritten. Records are selected using the same publication rules as pages: include/exclude, publish, and draft settings apply. Numbered records are sorted by their NNN prefix; 000 templates are excluded from the list. The directory defaults to the directory containing source. title and columns are required; required lists metadata fields required on each record. Set template to a template basename in that directory. When that named template is selected for publication, templateText is appended as a Markdown footer. Without template, templateText is always appended. The template file's contents are not included.

Navigation sections can contain nested children; titles must be globally unique across all levels. Child matches are checked before their parent match. An index points to the section's README source and controls its link ordering; first lists sources to prioritize among pages tied by weight. Pager scope is section for a child section, group for a top-level section and all its children, site for all pages, or none to disable adjacent-page links.

README.md and index.md map to their directory's index.html. Both in the same directory cause a collision error. Overlapping routes, file/directory conflicts, assets, aliases and reserved generator files are checked together.

YAML front matter

Front matter is optional. A page without it uses matching configuration defaults, its first H1 as the title, and the default route. Existing Markdown remains readable in GitHub without requiring front matter.

---
title: Streaming guide
description: Process a large input incrementally.
navTitle: Streaming
section: Guides
weight: 20
layout: doc
permalink: /guide/streaming/
aliases:
  - /docs/streaming.html
publish: true
draft: false
nav: true
search: true
toc: true
---
Field Meaning
title, description Page title and description metadata; title does not replace the Markdown H1
navTitle Short navigation label; falls back to title
section Override the matched section; unknown names form additional groups
weight Integer sort priority, lower first; absent defaults to zero
layout home or doc; home emphasizes the introductory heading
permalink Site-relative route ending in / or .html
aliases Previous routes that redirect directly to this page
publish False omits the page even in draft previews
draft True omits the page unless drafts are enabled
nav False hides it from navigation and adjacent-page links
search False excludes it from the local search index
noIndex True asks search engines not to index it and omits it from the sitemap
toc Override the global TOC default

Precedence is engine defaults, matching config defaults in order, then page front matter. Explicit false and zero override earlier values. Unknown keys, incorrect types and unclosed front matter fail builds; duplicate YAML keys fail. Excluded files stay excluded even if their metadata says publish: true.

Navigation sorts by weight, then places a section's configured index source and first-listed sources before other equal-weight pages, then places README introductions before remaining pages and sorts by source path. Previous/next links use the configured pager scope and skip pages hidden from navigation. Heading IDs default to unicode, preserving Unicode, removing punctuation, replacing whitespace with hyphens and suffixing duplicates with -1, -2, and so on. github uses GitHub-compatible slug rules; goldmark uses Goldmark's automatic IDs. The TOC includes H2 through H4.

The markdown object controls extensions: rawHTML, alerts, headingLinks and math are disabled by default, and alignAttributes defaults to true. With math, $…$ and $$…$$ write formulas that KaTeX renders in the browser; the math specification defines the syntax and the assets. headingIDs accepts unicode (default), github, or goldmark. Set searchHeadings to include heading fragments in search results; it defaults to false.

Hiding a page from navigation or search does not make it private: its HTML is still published, and it appears in the sitemap unless it sets noIndex. Use publish: false, draft exclusion, or file exclusion to omit its contents.

Markdown links resolve against the source Markdown directory, even when the target has a custom permalink. Directory links resolve to a published README or index page. Local page and image targets and published heading fragments must exist. Links to excluded or unpublished pages fail the build; remove the link or use the repository's explicit external URL when linking authoring sources. External URLs are preserved without remote availability checks.

Include local images and downloads in assets, or put a playground, downloads and other static resources under staticDir. For example:

{
  "staticDir": "site/public",
  "styles": ["assets/project.css"],
  "scripts": ["assets/project.js"]
}

This copies site/public/assets/project.css to assets/project.css. Default assets are reserved; customize with additional CSS, JS or a complete template. Any executable browser assets are served locally. Customize Netlify's CSP when the project deliberately needs additional capabilities such as WebAssembly.

Aliases produce standalone HTML redirect pages and a Netlify _redirects file. They are omitted from navigation, search and sitemap. Redirect chains, external redirects and missing destinations are rejected. Redirect pages send users to the target route; they do not preserve arbitrary incoming query/fragment state.

Raw HTML in Markdown is disabled by default. Fenced code, tables, task lists, strikethrough and normal Markdown are supported through Goldmark GFM. Language- specific syntax highlighting, Mermaid rendering, API reference generation and WebAssembly builds are extension points rather than bundled project features. Record indexes can generate design and optimization record lists. Write custom pages with a template or explicitly copied static HTML. Custom HTML/template links and static HTML heading fragments are not validated by the Markdown link checker; verify those custom assets separately.

Output lifecycle

Input documents are read only. All output is planned, checked and rendered before replacement. A failed build preserves the previous usable output; a successful rebuild removes stale pages and assets.

The output directory must be new, empty, or contain the exact .zerosite marker written by this generator. The generator rejects unrelated nonempty output, repository roots/ancestors and symlinked output paths. Do not keep handwritten files in generated output. Changes should be serialized against a given output directory; concurrent builds to the same destination are unsupported.

Custom templates and repository layout

layout.html is the default Go html/template layout. A custom template receives View: .Config, .Page, trusted rendered .Body, .Groups, .TOC, .Previous, .Next, relative asset URLs, optional .Source and .Canonical, .NoIndex, plus .URL for relative output paths ({{call .URL "index.html"}}). Keep page content in .Body; do not treat metadata as trusted HTML. Custom templates and public assets are trusted project code.

The Go command, module, tests, embedded layout.html and static/ live at the repository root. site/config.json configures ZeroSite's own documentation site; site/build.sh builds it using the root command. The compiled binary embeds its fallback assets and runs independently of this checkout.

ZeroSite began as the site tooling of ornew/template-basic and uses that OSS documentation template itself. Adopting projects can pin a published commit without waiting for a tagged release.

Pinned builds and skill synchronization

Use the same revision for generation and the accompanying agent skill:

go run github.com/ornew/zerosite@FULL_COMMIT_SHA -repo . -config site/config.json -out site/dist
go run github.com/ornew/zerosite@FULL_COMMIT_SHA skill -out .agents/skills/zerosite
go run github.com/ornew/zerosite@FULL_COMMIT_SHA version

The command embeds SKILL.md and its configuration reference. skill installs only that version's files; it refuses unrelated files and symlink destinations. The default destination is .agents/skills/zerosite. The installed .zerosite-version records the module version. Commit installed skill files with the generator pin. The template's hack/update-zerosite-skill.sh synchronizes both and also updates the dependency of Go extension drivers.

Go extension drivers

Projects that need generated pages or custom rendering can use the sitegen package. sitegen.BuildWith runs the standard publication pipeline with Prepare, Decorate, View, and template-function hooks while retaining shared configuration, routing, link validation, navigation, search, and safe output replacement. The Go extension guide includes a complete example. The lower-level render and publish packages remain available for projects that need a custom pipeline. Pin the module and exported skill to the same revision.

Verification

go test ./...
go vet ./...

Tests exercise publication controls, metadata defaults, routing, source-relative links, fragments, assets, navigation, search data, custom templates, output ownership and preservation on failure. Also build the actual repository Markdown and inspect the site after changing layout or browser behavior.