Deployment
Host the static build/ output anywhere, and optionally ship aardvark itself as a single compiled binary.
Hosting the site
vark build produces a fully static build/ directory — HTML, one JS/CSS island
bundle, your assets, and the generated files. Deploy it to
any static host:
- Netlify / Vercel / Cloudflare Pages — build command
vark build, publish directorybuild. Node must be available in the build image (it is on all three) to bundle islands. - GitHub Pages / S3 / nginx — build in CI, then upload
build/. - Run it yourself —
vark serveserves the build (and an optional live MCP server) from one hardened process, ready to package as a Docker image behind a CDN.
Set baseUrl in aardvark.config.yaml to your production URL so sitemap.xml and
llms.txt use absolute links.
Caching
Cache fingerprinted assets for a long time. Root CSS/JS, files copied from
static/ or public/, theme/runtime assets under /_aardvark/, islands
bundles, generated PWA icons/manifests, OG images, and controlled downloads are
emitted with per-build filenames such as /img/logo-<sha>.png.
Keep route and discovery files revalidatable instead: page HTML, _headers,
_redirects, robots.txt, sitemap.xml, .well-known/**, auth.md,
llms*.txt, metadata.json, search-index.json, page .md siblings, and
encrypted .enc payloads stay at stable paths by design.
The fingerprint token is your git HEAD SHA. If you build where .git isn’t
present (a container image, or a CI job with a shallow/absent checkout), set the
AARDVARK_BUILD_SHA environment variable to the commit being deployed so every
build still gets a stable, unique token. Without it, aardvark falls back to a
content hash of your project sources — correct, but it rotates every asset URL
whenever any source file changes.
Redirects
Page aliases: and config redirects: (see Generated files)
make vark build write a _redirects file. Cloudflare Pages, Netlify, and
Vercel read it and serve true 301s; other hosts ignore it, but per-page aliases
still work everywhere through their HTML stub pages. On Cloudflare/Netlify a static
file takes precedence over a _redirects rule for the same path (Netlify can force
it with a trailing !, Cloudflare can’t), so an alias’s stub wins there unless you
disable stubs with aliases: { htmlStubs: false }.
Publishing under a subpath
By default a site owns the root of its domain (https://docs.example.com/). To publish it
as a section of a larger site instead — say at https://example.com/docs/ — set basePath:
basePath: /docs
Every internal link, asset URL, redirect, localized URL, the search index, and the on-disk
layout are then written for that prefix: pages land under build/docs/…, links point at
/docs/…, and the whole build/docs folder is self-contained — drop it into a parent site at
/docs, or serve it with vark serve / vark dev (both mount at the base path locally, so
local dev matches production). If baseUrl is also set it keeps carrying the origin
(https://example.com); aardvark folds the base path into the absolute URLs used by
sitemap.xml, canonical tags, and Open Graph so they stay correct.
Authors keep writing site-relative links (/guide/intro/) — the base path is added at build
time, so content stays portable if the prefix ever changes.
Password-protected pages: each encrypted page is cryptographically bound to its own URL,
which includes the base path. Adding or changing basePath on a site that already has
protected pages therefore requires a fresh full build (which re-encrypts every protected page)
— a stale .enc blob from before the change is bound to the old path and will fail to decrypt.
A normal vark build + redeploy handles this; just don’t ship an old .enc alongside the new
loader page.
Password-protected pages: each encrypted page is cryptographically bound to its own URL,
which includes the base path. Adding or changing basePath on a site that already has
protected pages therefore requires a fresh full build (which re-encrypts every protected page)
— a stale .enc blob from before the change is bound to the old path and will fail to decrypt.
A normal vark build + redeploy handles this; just don’t ship an old .enc alongside the new
loader page.
Where the origin-root files go
robots.txt, _redirects, _headers, and everything under /.well-known/ are only honored
at the true origin root. rootFiles controls where they land:
basePath: /docs
rootFiles: nested # nested (default) | root
nested(default) — self-contained: these files nest underbuild/docs/with everything else. Use when you drop the folder into a parent site that owns the domain’s realrobots.txt//.well-known/.root— writes those files at the true build root (with their paths pointed into/docs/…) while the pages nest underbuild/docs/. Use when this build is deployed as its own project that owns the origin but serves the app under/docs.
Custom 404 page
Create 404.md at the root of your content/ directory and aardvark renders it to
404.html at the site root — wrapped in your theme, with the header, footer and
search box, just like any other page:
---
title: Page not found
description: We couldn't find that page.
---
# Page not found
The page you're looking for doesn't exist. Head back to the [home page](/).
It’s authored like a normal page (Markdown, `` tags and components all work),
but it’s treated specially: it’s served at the literal 404.html instead of a
pretty /404/ URL, marked noindex, and kept out of the nav, sitemap, search
index and llms.txt. Only a 404.md at the content root is special — a
nested guide/404.md stays an ordinary /guide/404/ page.
Most static hosts serve /404.html automatically for unmatched URLs (GitHub
Pages, Netlify and Cloudflare Pages do out of the box; on S3 set the error
document to 404.html; on Cloudflare Workers static assets set
assets.not_found_handling to "404-page"). vark dev serves it too, so you
can preview your error page by visiting any URL that doesn’t exist.
Multilingual sites: put your 404.md at the base-language content root —
that’s the /404.html every host serves for unmatched URLs, so it’s what visitors
see regardless of the URL they tried. A 404.md in a translated source dir (e.g.
content-fr/404.md) is still built, to /fr/404.html, but most hosts ignore it
and fall back to the root page. The exception is Cloudflare Workers’
"404-page" mode, which serves the nearest 404.html up the path — so there a
miss under /fr/ returns /fr/404.html when it exists. Unless your host does that
nearest-match lookup, a single base-language 404.md is all you need.
Shipping aardvark as a binary
Compile the tool itself to a single executable with Nuitka:
scripts/build-release.sh # -> dist/vark (single file)
The output lands in dist/ by default; set BUILD_OUT_DIR to write it elsewhere.
(For local iteration, scripts/build-binary.sh is a faster non---onefile
build that outputs a folder instead of a single file.)
The binary bundles the default theme and the islands runtime, so it works with
no Python environment. Node remains a runtime prerequisite on whatever
machine runs vark build, because islands are bundled with esbuild at build
time. Use vark build --no-bundle if you need to build without Node (components
become inert placeholders).