@rettangoli/sites
v1.2.0
Published
Generate static sites using Markdown and YAML for docs, blogs, and marketing sites.
Maintainers
Readme
Rettangoli Sites
@rettangoli/sites is the static-site engine used by Rettangoli. It renders pages from YAML and Markdown into _site/, with support for templates, partials, global data, collections, and watch mode.
It can run directly with bunx rtgl, so a site-level package.json is optional.
Quick Start
# scaffold
bunx rtgl sites init my-site
# run
cd my-site
bunx rtgl sites buildPackage Contract
my-site/
pages/ # YAML or Markdown pages (with optional frontmatter)
templates/ # YAML templates
partials/ # YAML partials
data/ # Global YAML data
static/ # Static assets copied to _site/
sites.config.yaml # Optional site settings
_site/ # Generated outputWhat It Supports
- YAML pages rendered through
jempl+yahtml - Markdown pages rendered through
markdown-it+ Shiki (defaultrtglMarkdown) - Frontmatter (
template,url,tags, arbitrary page metadata) - Global data from
data/*.yamland optional inlinesites.config.yaml data - Collections built from page tags
$if,$for,$partial, template functions- Static file copying from
static/to_site/ - Default sitemap generation when
data.site.baseUrlis configured - Watch mode with local dev server + websocket reload
Site Config
Use sites.config.yaml (or sites.config.yml) with top-level markdownit for supported settings.
Legacy key markdown is still accepted as an alias.
markdownit:
preset: default
html: true
xhtmlOut: false
linkify: true
typographer: false
breaks: false
langPrefix: language-
quotes: "\u201c\u201d\u2018\u2019"
maxNesting: 100
shiki:
enabled: true
theme: slack-dark
codePreview:
enabled: false
showSource: true
theme: slack-dark
headingAnchors:
enabled: true
slugMode: unicode
wrap: true
fallback: section
build:
keepMarkdownFiles: false
imports:
templates:
base: https://example.com/templates/base.yaml
docs: https://example.com/templates/docs.yaml
partials:
docs/nav: https://example.com/partials/docs-nav.yaml
data:
site:
baseUrl: https://example.com
themeCssHref: /public/theme.css
themeBodyClass: dark
sitemap:
outputPath: sitemap.xml
defaults:
changefreq: weekly
priority: 0.5
exclude:
- /drafts/*
pages:
/:
priority: 1In the default starter template, CDN runtime scripts are controlled via data/site.yaml:
assets:
loadUiFromCdn: true
loadConstructStyleSheetsPolyfill: trueEnable codePreview if you want fenced blocks like ```html codePreview to render a live preview panel.
Use showSource to show/hide the source pane and theme to override the highlight theme for preview blocks.
Set build.keepMarkdownFiles: true to keep source Markdown files in output in addition to generated HTML.
Example mappings:
pages/index.md->_site/index.htmland_site/index.mdpages/docs/intro.md->_site/docs/intro/index.htmland_site/docs/intro.md
For Markdown pages with a custom url, the copied .md file follows the custom URL path.
For example, url: /guides/start/ writes _site/guides/start/index.html and _site/guides/start.md.
Pages use their file path as the URL by default:
pages/index.*->/pages/about.*->/about/pages/docs/intro.*->/docs/intro/
Set url in page frontmatter to override that path:
---
title: Company
url: /company/
---url is normalized to a site-relative clean URL with a leading and trailing slash, so company becomes /company/.
External URLs, query strings, fragments, whitespace, and . / .. path segments are rejected.
Duplicate page URLs are rejected after normalization.
Sitemap
Sites writes _site/sitemap.xml by default when data.site.baseUrl is configured. Use sitemap in sites.config.yaml to customize output, or set sitemap: false to disable it.
data:
site:
baseUrl: https://example.com
sitemap:
outputPath: sitemap.xml
defaults:
changefreq: weekly
priority: 0.5
exclude:
- /drafts/*
pages:
/:
priority: 1
changefreq: daily
lastmod: "2026-05-25"
/private/: falseIf you do not use data.site.baseUrl, set sitemap.siteUrl instead.
Generated entries use normalized page URLs, including page frontmatter url overrides.
Use page frontmatter for per-page control:
---
sitemap:
changefreq: monthly
priority: 0.8
lastmod: "2026-05-25"
---Set sitemap: false in page frontmatter to exclude one page.
sitemap.exclude accepts exact page URLs and prefix patterns ending in *, such as /drafts/*.
imports lets you map aliases to remote YAML files (HTTP/HTTPS only). Use aliases in pages/templates:
- page frontmatter:
template: baseortemplate: docs - template/page content:
$partial: docs/nav
Use top-level data in sites.config.yaml for small global values that do not deserve their own data/*.yaml file.
sites.config.yaml data and data/*.yaml are merged, with data/*.yaml winning on conflicts.
Inline config data requires rtgl >= 1.1.4 or @rettangoli/sites >= 1.0.3.
Imported files are cached on disk under .rettangoli/sites/imports/{templates|partials}/ (hashed filenames).
Alias/url/hash mapping is tracked in .rettangoli/sites/imports/index.yaml.
Build is cache-first: if a cached file exists, it is used without a network request.
When an alias exists both remotely and locally, local files under templates/ and partials/ override the imported one.
If you want to publish a manual llms.txt, place it in static/llms.txt; it will be copied to _site/llms.txt.
System Frontmatter
Use _bind to map global data keys into page-local variables.
Example:
---
template: base
_bind:
docs: feDocs
---This resolves docs from data/feDocs.yaml for that page.
_bind is a system property and is not exposed to templates directly.
Rules:
_bindmust be an object- each
_bindvalue must be a non-empty string - each
_bindvalue must point to an existingdata/*.yamlkey _bindis removed from public frontmatter before rendering/collections
Binding order:
- build page context from
deepMerge(globalData, frontmatterWithoutSystemKeys) - apply
_bindaliases on top (alias wins for that key)
Reusable Asset Package
@rettangoli/sites is the engine only.
Reusable themes, templates, partials, helper assets, schemas, and VT coverage now live in packages/rettangoli-sitekit/ and publish from @rettangoli/sitekit.
Use @rettangoli/sitekit when you want curated importable site assets.
Keep @rettangoli/sites for build/watch/init behavior.
Template Authoring Pattern
Keep base templates as shells with minimal logic:
- document root (
html,head,body) - main content slot (
"${content}") - stable layout containers
Put variant-specific behavior and data wiring in partials instead.
Partials accept explicit parameters via $partial, so they are the preferred place for:
- section-specific navigation data
- conditional UI branches
- reusable interactive blocks
This keeps one template reusable across many page variants and avoids duplicated template files.
Commands
bunx rtgl sites build
bunx rtgl sites watch
bunx rtgl sites build --quiet
bunx rtgl sites watch --quiet
bunx rtgl sites watch --reload-mode full
bunx rtgl sites build --root-dir . --output-path dist
bunx rtgl sites watch --root-dir . --output-path dist --reload-mode full--reload-mode body (default) does fast body replacement; --reload-mode full forces full page refresh.
--root-dir/--output-path are the preferred option names (--rootDir/--outputPath remain as legacy aliases).
Built-in Template Functions
Available in YAML templates/pages without extra setup:
encodeURI(value)encodeURIComponent(value)decodeURI(value)decodeURIComponent(value)jsonStringify(value, space = 0)formatDate(value, format = "YYYYMMDDHHmmss", useUtc = true)now(format = "YYYYMMDDHHmmss", useUtc = true)sort(list, key, order = "asc")chunk(list, size = 1, pad = false, fillValue = null)md(content)toQueryString(object)
formatDate tokens: YYYY, MMM, MM, DD, D, HH, mm, ss.
decodeURI/decodeURIComponent return the original input when decoding fails.
sort supports order as asc or desc (default: asc), accepts dot-path keys (for example data.date), and returns a new array.
chunk splits arrays into rows of size; with pad = true, the last row is padded with fillValue.
md returns raw rendered HTML from Markdown for template insertion.
Screenshots
@rettangoli/sites builds pages; screenshot capture is handled by @rettangoli/vt.
Use VT against your generated site:
- Add
vt/specs/*.htmlspecs (use frontmatterurlfor the page to capture). - Add
vtconfig inrettangoli.config.yaml. - Run
rtgl vt generate,rtgl vt report, andrtgl vt accept.
Docker runtime (recommended for stable Playwright/browser versions):
IMAGE="han4wluc/rtgl:playwright-v1.57.0-rtgl-v1.1.0"
docker pull "$IMAGE"
docker run --rm -v "$PWD:/workspace" -w /workspace "$IMAGE" rtgl vt screenshot
docker run --rm -v "$PWD:/workspace" -w /workspace "$IMAGE" rtgl vt report
docker run --rm -v "$PWD:/workspace" -w /workspace "$IMAGE" rtgl vt acceptExample:
vt:
path: ./vt
url: http://127.0.0.1:4173
service:
start: bun run preview
sections:
- title: pages
files: .---
title: home
url: /
---
<div></div>bun run preview (or any equivalent local server command) must serve your built site on vt.url (for example serving _site/ on port 4173).
For a maintained example asset pack and VT lab, see packages/rettangoli-sitekit/.
Full Architecture And Analysis
See docs/architecture-and-analysis.md for:
- End-to-end rendering flow
- Data/context model used during render
- URL/output mapping rules
- Config contract details
- Full robustness analysis and prioritized improvements
