Skip to content

Deployment — Cloudflare (docs site)

The only deploy target at v0.1.0. A VitePress static site built from docs/ and shipped as a Cloudflare Worker with Static Assets via the Workers Builds flow (the unified successor to classic Cloudflare Pages).

All doc-related config lives inside docs/ — nothing docs-specific is placed at the repo root. Cloudflare's project root is set to docs/.

Pipeline

  1. Push to main (or open a PR — Cloudflare creates a preview URL per branch).
  2. Cloudflare Builds picks up the commit and cds into the configured root directory (docs/).
  3. Install: npm install — reads docs/package.json.
  4. Build: npm run buildvitepress build, writes static output to docs/.vitepress/dist/.
  5. Deploy: npx wrangler deploy reads docs/wrangler.jsonc and uploads .vitepress/dist/ as the Worker's static assets.
  6. Cloudflare serves the assets on the *.workers.dev subdomain (and any custom domain attached to the Worker).

Config

All of these live inside docs/:

  • docs/package.json — pins vitepress and defines dev / build / preview scripts.
  • docs/.vitepress/config.mts — site title, nav, sidebar, edit links, search.
  • docs/wrangler.jsonc — Worker name, compatibility date, and assets.directory pointing at ./.vitepress/dist. Without this file npx wrangler deploy has nothing to deploy.
  • docs/.nvmrc — pins the Node major version. Without it the builder may pick a Node older than VitePress 1.x supports.
  • docs/.gitignore — keeps node_modules/, .vitepress/dist/, and .vitepress/cache/ out of git.

wrangler.jsonc — the assets-only Worker

jsonc
{
  "name": "lavender-2-docs",
  "compatibility_date": "2025-03-01",
  "assets": {
    "directory": "./.vitepress/dist",
    "not_found_handling": "404-page",
    "html_handling": "auto-trailing-slash"
  }
}
  • No main / no fetch handler. With assets and nothing else, Cloudflare serves the directory as pure static content; no JavaScript runs at the edge.
  • not_found_handling: "404-page" — serves the VitePress-built 404.html for any unknown path.
  • html_handling: "auto-trailing-slash" — matches VitePress's cleanUrls: true so /training/v0 and /training/v0/ both resolve to training/v0.html.

One-time setup (repo owner)

  1. https://dash.cloudflare.com/Workers & PagesCreateImport a repository (Workers Builds).
  2. Pick the Lavender-2 repository.
  3. Configure the build:
    • Production branch: main
    • Root directory: docs
    • Build command: npm run build
    • Deploy command: npx wrangler deploy
    • Build output directory (if asked): .vitepress/dist
  4. Environment variables (optional):
    • NODE_VERSION = 22 — belt-and-braces complement to docs/.nvmrc.
  5. Save and Deploy. The first deploy takes a minute or two; subsequent pushes to main auto-deploy and non-main pushes get a preview URL on the Worker.

Local preview

bash
cd docs
npm install
npm run dev      # live-reload dev server on http://localhost:5173

Production build + preview:

bash
cd docs
npm run build
npm run preview

Manual deploy (requires wrangler login):

bash
cd docs
npm run build
npx wrangler deploy

Pitfalls

  • Do not move config files up to the repo root. The deployment contract is that docs/ is a self-contained VitePress + Wrangler project. Anything at the root confuses both Cloudflare's root- directory setting and readers looking for the docs source of truth.
  • If the build step succeeds but npx wrangler deploy errors, check docs/wrangler.jsonc first. The failure mode looks like a successful VitePress build followed by wrangler running with nothing to deploy — that means either the config is missing or assets.directory doesn't match the VitePress output path.
  • Node version matters. VitePress 1.x requires Node 18+; the Cloudflare Builds default may be older. docs/.nvmrc handles this, but if you override it locally keep it ≥ 18.
  • Do not commit docs/node_modules/ or docs/.vitepress/dist/.docs/.gitignore already covers both; keep it.
  • VitePress cleanUrls: true means internal links should not include a .html suffix. Markdown .md links work as-is.
  • Dead-link checking is loose by default (ignoreDeadLinks: true in the config) because several in-page links reference GitHub blobs by line number. Flip that flag locally before shipping if you want a stricter check.