Development#
A practical guide to working on an Ovellum site: scaffolding, writing, rebuilding on change, checking links, and serving the output locally before you ship.
For deployment targets, jump to the Deploy guide. For Ovellum's own repo (the monorepo, packages, tests), see Contributing.
Prerequisites#
- Node 20 or newer — Ovellum targets Node 20 LTS.
- A package manager — pnpm, npm, and yarn all work. The commands
below use
npx, which is shipped with npm. Substitutepnpm dlxoryarn dlxto taste.
You don't need to install Ovellum globally. npx ovellum <command> will
download and cache it on first run; subsequent runs use the cache.
Scaffold a new site#
mkdir my-docs && cd my-docs
npx ovellum init
The init prompt asks for a project name, mode (manual for a docs
site, auto for API generation, hybrid for both), title, description,
input/output paths, and the default theme. Press Return through the
defaults if you're just trying it out.
Non-interactive scaffold (everything takes its default, useful in CI or quick scripts):
npx ovellum init --yes
Files written:
ovellum.config.json— your one config file.content/index.md— a starter page (manual + hybrid modes only)..gitignore— getsdist/and.orphans/appended if absent.
See the CLI reference for init for the
full flag list and exit codes.
Write#
Drop .md files anywhere under your input/ directory (default
content/). The shape:
content/
index.md ← becomes /
getting-started.md ← becomes /getting-started/
guides/
install.md ← becomes /guides/install/
deploy.md ← becomes /guides/deploy/
_meta.json ← optional: directory title + page order
Page titles, descriptions, and other metadata go in YAML frontmatter:
---
title: Getting started
description: Install Ovellum and build your first docs.
---
# Getting started
…
_meta.json per directory controls grouping and order:
{
"title": "Guides",
"order": ["install", "configure", "deploy"]
}
See _meta.json
in the config reference for the full spec.
Iterate with ovellum dev#
The dev command is the heart of the writing loop. One command does
everything: initial build, watch for changes, serve on
http://127.0.0.1:3000/, and auto-refresh open browser tabs on every
rebuild.
npx ovellum dev
What happens:
- Initial build on start.
- HTTP server starts on the next available port (3000 by default; bumps if busy).
- Watcher runs the rebuild on every change under
input/— debounced 300 ms, withchokidar'sawaitWriteFinishenabled so partial writes from your editor don't trigger half-state rebuilds. - Live reload pushes a Server-Sent Event to every connected browser
tab on each successful build; the tiny client script injected into
HTML responses calls
location.reload(). - Config reload when
ovellum.config.*itself changes — the next build picks up the new settings. - Clean shutdown on
Ctrl-C.
Open the URL it prints and start editing. Save → see the page refresh within ~100 ms.
Flags#
npx ovellum dev --port 4000 # pick a port
npx ovellum dev --host 0.0.0.0 # expose on the local network
npx ovellum dev --cwd ./website # multi-site monorepo
See the CLI reference for dev for the
full flag list.
Manual two-process loop (optional)#
ovellum dev covers the common case. If you want to run your own
server (a CDN emulator, a reverse proxy, a process manager that
restarts the watcher), the primitives are still available:
# Terminal 1: watch only
npx ovellum watch
# Terminal 2: serve the dist/ directory
npx ovellum serve
# …or any other static server you prefer:
npx serve dist
python3 -m http.server -d dist 8000
npx http-server dist -c-1 # -c-1 disables caching
ovellum serve is a pure static server — no watching, no reload
injection. Cache headers are public, max-age=0 so refreshes still
fetch the latest build.
Check before you ship#
ovellum check is the lint pass: no writes, exits non-zero if anything
looks wrong. It currently catches:
- Broken internal links — any
/foo/or./bar.mdthat doesn't resolve to a page in the sidebar. - Unsafe URL schemes —
javascript:,vbscript:,data:,file:. These get stripped at render time anyway (see Security), butchecksurfaces them as[SECURITY]issues so authors notice and remove them.
npx ovellum check
Output is a small per-file table with line numbers. Wire it into your
CI alongside build and you'll catch link rot before it ships.
See the CLI reference for check for
the exact output shape and exit codes.
Build for production#
ovellum build is what your deploy pipeline runs:
npx ovellum build
In manual mode it produces:
dist/*.html(and nested directories withindex.htmlfor pretty URLs).dist/assets/ovellum.css+dist/assets/ovellum.js.dist/sitemap.xmlwhensite.baseUrlis configured.dist/pagefind/whensite.search.enabledistrue.dist/404/index.htmland (via post-build script)dist/404.html— whichever your host looks for.
Build output is fully deterministic given the same inputs. Two runs back-to-back produce byte-identical files.
Recommended workflow#
The whole loop, end-to-end:
npx ovellum init # once, when starting
git init && git add . && git commit -m 'scaffold'
npx ovellum dev # while writing — keep running
npx ovellum check # before committing
npx ovellum build # once, before pushing — confirms a cold build works
git add . && git commit -m 'docs: explain how the merger works'
git push # CI runs `ovellum build` and deploys
One terminal for the dev loop, no juggling.
Working with multiple sites in one repo#
If your repo hosts both code and docs (a monorepo, for example), pass
--cwd to scope each Ovellum command to a sub-directory:
npx ovellum dev --cwd ./website
npx ovellum build --cwd ./website
npx ovellum check --cwd ./website
Every path in ovellum.config.json resolves relative to that --cwd,
so you can keep the docs entirely self-contained while sharing the
repo.
What's next#
- Deploy guide — GitHub Pages, Netlify, Vercel, Cloudflare, Nginx, S3, anywhere.
- Themes — what's bundled, what to customise, and how the icon registry works.
- Configuration reference — every field, every default.