Qubik's Site

Building & Deploying

The site is a static VitePress build — any static host works. This page covers the build, the few things this theme needs from a host or a CI job, and a pre-launch checklist.

Build and preview

sh
sh
pnpm build     # → .vitepress/dist
pnpm preview   # serve the build locally

.vitepress/dist is what you deploy: plain HTML, CSS, JS, and assets, with no server runtime.

Restart pnpm preview after every rebuild. It snapshots the asset list at startup, so a re-hashed asset from a newer build will 404 in a preview server that is still running.

Hosting requirements

RequirementWhy
Static file servingThe build is entirely static
Files up to ~11 MB per assetThe Typst math compiler is the largest asset; the build already compresses it
Correct MIME types for .wasm / .wasm.gzTypst math loads the compiler at runtime

No Node runtime, no rewrite rules, no edge functions.

Clean URLs

The demo site sets cleanUrls: true, so generated links are /guide/getting-started rather than /guide/getting-started.html. The build still writes the .html files, so:

  • Existing .html links keep resolving directly.
  • Suffix-free links rely on the host's extension fallback — every mainstream static host (Cloudflare Pages, Netlify, Vercel, GitHub Pages, nginx with try_files) does this.
  • Arriving on a .html URL rewrites the address bar to the clean form in place, with no navigation and no extra history entry.

Details and the host table: configuration/clean-urls.md. Setting cleanUrls: false is fine too — the theme works either way.

Deploying to a subpath

If the site is not at a domain root (GitHub Project Pages, for instance), set VitePress's base:

ts
ts
export default defineConfigWithTheme<TerminalThemeConfig>({
  base: "/my-site/",
  // …
});

The theme's links, active-tab matching, and heading-anchor copying all honor base — but your own content must too: write internal links site-absolutely (/posts) and let VitePress resolve them, rather than hardcoding the prefix.

Large assets: the Typst compiler

The Typst math compiler is a ~28 MB WebAssembly module — larger than several hosts allow (Cloudflare Pages caps files at 25 MiB). The build handles this without configuration: any bundled .wasm over 24 MiB is re-emitted gzipped as <name>.wasm.gz (~10.7 MB), and the client decompresses it before initializing the compiler.

Two consequences:

  • Serve .wasm.gz as an opaque binary. If your host transparently decodes it, that still works — the client checks the actual bytes rather than trusting the extension.
  • The dev server serves the raw .wasm; this only applies to builds.

If you don't use Typst math, nothing changes — the compiler is loaded lazily, only on pages that contain Typst blocks.

Continuous deployment

Two things this theme needs from a CI checkout:

  1. Full git history (fetch-depth: 0) if you use the git-derived "Updated" date on articles. A shallow clone silently produces no timestamps.
  2. Submodules (submodules: recursive) if your friend-link data comes from a generator repository.

A GitHub Actions job, as a starting point:

yaml
yaml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # git-derived "Updated" dates
          submodules: recursive   # friend-link data
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      # → upload .vitepress/dist to your host

Vercel

Vercel's built-in VitePress preset assumes the other common layout — a project whose site lives in a docs/ folder — so it builds with vitepress build docs and then looks for the output in docs/.vitepress/dist. This theme keeps its config at the repository root and its content in src/, so the build writes .vitepress/dist instead, and a preset-driven deploy fails after a green build with "No Output Directory named docs/.vitepress/dist found".

The repository therefore ships a vercel.json that pins the real values:

json
json
{
  "framework": "vitepress",
  "installCommand": "pnpm install --frozen-lockfile",
  "buildCommand": "pnpm build",
  "outputDirectory": ".vitepress/dist",
  "cleanUrls": true
}

cleanUrls there is Vercel's own setting, and it mirrors the site's cleanUrls: true: /docs/guide/getting-started.html redirects to the suffix-free URL rather than being served at both addresses.

Two more things worth knowing about Vercel specifically:

  • Enable Git submodules in the project settings if your friend-link data comes from a generator repository — the build succeeds without them, it just finds no generated links.
  • The git-derived "Updated" dates need full history. Vercel clones shallowly by default; without deeper history those timestamps come out empty.

vercel.json wins over the dashboard's Build & Development Settings, so keep changes in the file rather than the UI.

Services to set up separately

Two features depend on infrastructure outside the site:

  • Search. Point themeConfig.search.algolia at your DocSearch index. The crawler needs the deployed site, so the usual order is: deploy → apply for or configure DocSearch → add the keys → redeploy. Use the search-only key. Without keys the palette opens and says it isn't configured.
  • Comments. Deploy a Waline server and set themeConfig.comments.waline.serverURL. Without it, no comment card and no view/comment counts render.

Pre-launch checklist

  • [ ] title, description, and lang set in the site config; themeConfig.title / .description set if you serve more than one language
  • [ ] mainColor is yours
  • [ ] author and license set — they drive the footer, prompts, and every article's license card
  • [ ] Demo content removed from src/, demo entries removed from .vitepress/config.mts, DemoLayout swapped out in .vitepress/theme/index.ts
  • [ ] favicon.svg replaced in src/public/
  • [ ] Placeholder footer.rss and comments.waline.serverURL values replaced or removed
  • [ ] pnpm build clean, then pnpm preview checked in dark, light, and paper modes and at a 360 px viewport
  • [ ] Print one article (paper mode is the print stylesheet)
READ~/docs/guide/deployment
TOPdark