Swifty is a convention-first static site generator for documentation, blogs,
brochure sites, and other content-focused websites. Put Markdown in pages/,
add layouts when you need them, and get fast, deployable HTML without assembling
a plugin stack first.
It requires Node.js 22 or newer.
- A useful starter, not an empty directory. New sites include a responsive stylesheet, default layout, page template, and tiny JavaScript example.
- Content structure is site structure. Folders become routes and drive layouts, navigation, breadcrumbs, sibling links, and feeds.
- Documentation features are built in. Stable heading anchors, generated tables of contents, syntax highlighting, search, and link validation work without external services.
- Publishing has sensible controls. Drafts, scheduled pages, deterministic dates, summaries, related content, pagination, RSS, sitemaps, and social tags share the same content model.
- The browser runtime stays local. Search, Morpheus navigation, Idiomorph, and highlight.js themes are copied into the generated site rather than loaded from a CDN.
- There is a small escape hatch. Optional
swifty.config.jscan expose Eta globals/helpers or register Marked extensions when conventions are not enough.
npm install -g @daz4126/swifty
swifty new my-site
cd my-site
swifty startOpen http://localhost:3000. The development server rebuilds and refreshes the browser as files change.
The scaffold looks like this:
my-site/
├── pages/
│ └── index.md
├── layouts/
│ └── default.html
├── partials/
├── css/
│ └── style.css
├── js/
│ └── hello-swifty.js
├── images/
├── data/
├── public/
├── template.html
├── swifty.config.js # Optional; add only when needed
└── config.yaml
pages/
├── index.md → /
├── about.md → /about
├── 404.md → /404.html
└── docs/
├── index.md → /docs
└── getting-started.md → /docs/getting-started
A page is Markdown with optional YAML front matter:
---
title: Getting Started
summary: Install and configure the project.
tags: [docs, setup]
---
# Getting Started
Write normal **Markdown** here.Folder names select matching layouts and link partials by convention. A page in
pages/docs/ uses layouts/docs.html when it exists; a partials/docs.md
customizes generated child and sibling links for that section.
Swifty uses Eta syntax in template.html, layouts, partials, and Markdown:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= title %> · <%= sitename %></title>
<%= og_tags %>
</head>
<body>
<nav><%= nav_links %></nav>
<main><%= content %></main>
</body>
</html>Include project partials with <%= partial: name %>. CSS and JavaScript files
in their respective folders are minified, cache-busted with modification times,
and injected automatically.
Useful page variables include:
| Variable | Purpose |
|---|---|
<%= breadcrumbs %> |
Folder-aware breadcrumb links |
<%= links_to_children %> |
Child pages for a folder index |
<%= links_to_siblings %> |
Other pages in the current section |
<%= links_to_tags %> |
Canonical generated tag links |
<%= prev_page %> / <%= next_page %> |
Ordered sibling navigation |
<%= summary %> |
Authored or automatically extracted summary |
<%= related_pages %> |
Pages ranked by shared tags |
<%= word_count %> / <%= reading_time %> |
Prose metrics; fenced code is excluded |
<%= toc %> |
Nested links to generated heading IDs |
pages / collections.pages |
Immutable authored-page metadata collection |
Add the self-hosted search interface anywhere:
<%= partial: search %>Swifty generates /search.json; search_content_limit bounds normalized body
text per entry while titles, URLs, summaries, and tags remain complete.
Every Markdown heading gets a stable ID. Put <%= toc %> in a long-form layout
to render an accessible outline, and use ordinary links such as
[Configuration](#configuration) for deep linking.
Build recent-post lists, archives, or custom homepages from the page collection:
<% for (const post of collections.pages.filter((page) => page.tags.includes("news")).slice(0, 5)) { %>
<article>
<h2><a href="<%= post.url %>"><%= post.title %></a></h2>
<p><%= post.summary %></p>
</article>
<% } %>Collection entries include title, URL, summary, tags, and display and ISO dates. Generated routes, 404 pages, drafts, and future pages are excluded from production collections.
---
title: Work in Progress
draft: true
---Draft and future-dated pages appear during swifty start. Preview them in a
standalone build with:
swifty build --drafts --out previewA normal production build excludes them. Future pages publish on the first build after their configured date; use scheduled deployments when publication must happen without a content commit.
Defaults are intentionally usable. A typical config.yaml might contain:
sitename: My Site
site_url: https://example.com
base_path: ""
highlight_theme: monokai-sublime
search: true
search_content_limit: 5000
search_results_limit: 10
morphing: true
prefetching: true
morph_target: main
navigation_cache_size: 20
navigation_cache_ttl: 15
date_locale: en-GB
timezone: UTC
words_per_minute: 200
watcher_use_polling: false
build_concurrency: 16Native filesystem events are the default. Enable watcher_use_polling only for
cloud folders, network mounts, container volumes, or filesystems that miss
events. Pagination is also explicit: set page_count globally or in a folder's
config.yaml when that section needs it.
See the configuration guide for every option, including RSS feeds, responsive images, minification, date formatting, and base-path deployments.
Most sites only need layouts, partials, and data files. For small reusable hooks,
add swifty.config.js:
module.exports = {
globals: {
productName: "My Site",
},
helpers: {
uppercase(value) {
return String(value).toUpperCase();
},
},
markedExtensions: [],
};Then use <%= uppercase(productName) %> in any Eta template. Projects with
"type": "module" use export default { ... }. Restart swifty start after
editing the extension file because it loads when the process starts.
| Command | What it does |
|---|---|
swifty new <name> |
Create a styled starter site |
swifty start [--out dir] |
Build, serve, watch, and live reload |
swifty build [--out dir] |
Create a clean production build |
swifty build --drafts |
Include draft and scheduled pages in a preview |
swifty check |
Validate config, templates, routes, links, anchors, and assets |
swifty deploy ["message"] |
Build, commit only generated output, and push |
swifty --help |
Show CLI usage |
swifty --version |
Show the installed version |
Unknown commands fail safely; they never create directories implicitly.
Run this before deployment:
swifty check
swifty buildswifty check renders into a temporary directory without changing dist/. It
reports malformed configuration or front matter, duplicate routes, broken
internal links and heading anchors, missing images, partials and layouts, and
invalid canonical/social metadata.
Production builds minify HTML, CSS, and JavaScript; optimize local raster images to responsive WebP output; generate search, RSS, sitemap, and robots files; and omit development scripts. The result is ordinary static files deployable to any static host.
Swifty also exports its build and check functions as ESM:
import build, { checkSite } from "@daz4126/swifty";
await build();
const report = await checkSite();The current API is intentionally one-site-per-process because configuration, indexes, and caches are module-scoped. Use a separate process per independent site.
- Getting started
- Complete tutorial
- Configuration
- Pages and template variables
- Migration guide
- Roadmap
npm install
npm test
npm run test:package
npm run buildSwifty is released under the MIT License.