static_

How it works

Rendering

static build starts an embedded Vite server (no HTTP port, no bundle) with @vitejs/plugin-vue, loads each page and component through ssrLoadModule, and renders it with Vue's server renderer to HTML strings. The result is static HTML: nothing hydrates.

  • Projects have no node_modules. vue and vue/server-renderer are aliased to the CLI's copies and evaluated inside Vite's runner, so page components and the renderer share a single Vue instance.
  • A small plugin blanks the --- frontmatter before Vue compiles the file (keeping line numbers) and extracts <script client> blocks, keyed by file. A mixin records which components rendered, so only their scripts ship.
  • Vue warnings for "failed to resolve component" and "property not defined" are turned into build errors.
  • Components in components/ and the CLI's lib/builtins/components/ are registered globally by PascalCase file name. Project files win over built-ins.

Document assembly

The layout's output is wrapped by lib/render/document.js, which adds the doctype, lang, charset, viewport, SEO tags (lib/seo.js), the theme init script when a data-theme-toggle is present, the stylesheet link and the client scripts. A layout that returns its own <html> keeps its <body> classes.

Build pipeline (lib/build/index.js)

  1. Load .env*, site.json, data/*.json (with ${VAR} interpolation).
  2. Process images (lib/images.js): images/ plus content/*/media/, AVIF/WebP variants cached in .build-cache, manifest used by <Image>.
  3. Compile CSS (lib/build/css.js): Tailwind 4 through @tailwindcss/postcss, or plain passthrough, fingerprinted.
  4. Load collections (lib/build/collections.js): Markdown to HTML with marked, note images resolved against the manifest, drafts filtered.
  5. Render pages, data-driven pages, collection entries, listings, tag pages.
  6. Generate sitemap, robots, 404, RSS, _headers, _redirects; copy public/; minify.
  7. Run checks; swap .build.tmp/<pid> into .build only if everything succeeded. A failed build never replaces a good one.

Dev server

static dev keeps one renderer alive and does a debounced full rebuild per change (about 150 ms), serves .build, and reloads the browser over server-sent events. Build errors are shown in the terminal and as an in-browser overlay.

Notes and posting

lib/notes.js writes a note file and copies its images; static note wraps it. Nothing else is involved: the build treats a note like any collection entry with style: "note". Posting from other tools only needs to produce the same file in the repo.

Deploy

commands/deploy.js builds, then dispatches to lib/deploy/netlify.js or lib/deploy/cloudflare.js, each of which just returns the CLI command to run (netlify deploy or wrangler pages deploy). .env files are loaded into the child's environment so deploy tokens never need to be exported by hand. static ci writes a GitHub Actions workflow that does the same on push.

Migrating from EJS

Older Static projects used EJS (.html pages, templates/base.html, <component name="x">). That is no longer supported and the build fails with a pointer here. To port: rename pages/x.html to x.vue, wrap the body in <template>, replace <%= x %> with {{ x }}, <% forEach %> with v-for, and templates/base.html with layouts/default.vue using <slot />.