CLI Reference
Complete reference for the zfb command-line interface — subcommands, flags, and environment variables.
Overview
The zfb binary exposes five subcommands for the full development lifecycle:
zfb new — scaffold a new project from a template
zfb dev — local development server with live reload
zfb build — production build
zfb preview — serve a previously built site
zfb check — typecheck and validate content-collection schemasRun zfb --help or zfb <subcommand> --help to see the generated help text directly.
zfb new
Scaffold a new project from a built-in template.
zfb new <name> [--template <template>]Arguments
| Name | Type | Required | Description |
|---|---|---|---|
name | positional | yes | Name of the new project. Used as the destination directory. |
--template | string | no | Template to scaffold from. Default: basic-blog. |
Templates
v0 ships two templates, both baked into the binary at compile time from crates/:
basic-blog(default) — full-featured blog scaffold withpackage.json;zfb newrunspnpm installfor it automatically when pnpm is onPATH.node-free— ships nopackage.jsonand skips the post-scaffoldpnpm installstep entirely, for projects run with no Node/pnpm onPATH. See Markdown and HTML Pages for the.md/.htmlpage-authoring surface, and Install without Node for installing thezfbbinary itself without Node.
Example
zfb new my-site
zfb new my-site --template basic-blog
zfb new my-site --template node-freezfb dev
Start the local development server with live reload.
zfb dev [--port <port>] [--host [<host>]]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--port | u16 | 3000* | Port to bind the dev server to. |
--host | string | localhost* | Host interface to bind to. Bare --host (no value) is a shortcut for 0.0.0.0. |
* Falls back through: CLI flag → port/host in zfb.config.json → built-in default.
Lazy rendering
The dev server defaults to lazy rendering: changed routes are marked stale and re-rendered on the first request rather than immediately on every file change. This keeps the inner loop fast for large sites.
To restore the pre-lazy behavior and re-render every affected route eagerly on each file change, set ZFB_DEV_EAGER=1.
Environment variables
| Variable | Values | Description |
|---|---|---|
ZFB_DEV_EAGER | 1 | Disable lazy dev rendering. Re-renders every affected route eagerly on each file change. |
ZFB_LAZY_DEV_RENDER | 0 or 1 | Precise override of the lazy dev-render switch. 1 forces lazy; 0 forces eager. Takes precedence over ZFB_DEV_EAGER when both are set. |
ZFB_DEV_BOOT_LAZY | 1 | Opt-in fast boot: when a valid prebuilt dist/ exists, serve it immediately and defer per-route rendering to the first request instead of rendering every route at boot. Requires lazy rendering (no-op when ZFB_DEV_EAGER is set). Warns and falls back to the eager boot render (hinting cold) when no servable dist/ exists. Off by default. |
ZFB_DEV_BOOT_LAZY | cold | Seedless boot-lazy: defers per-route rendering to the first request the same way, but without requiring a prebuilt dist/ at all. A route with no fallback artifact on disk serves the dev 404 page (with livereload) until its own first request — tabs left open during that window self-heal via livereload once each route resolves. A dist/ left over from an unrelated prior build is not required or checked here, but if one exists it still serves its (possibly stale) bytes ahead of the 404, same as Auto's checked seed does. Use when no dist/ exists yet; 1 would warn and fall back to the eager boot render in that case instead. |
ZFB_DEV_DEFER_BUNDLE | 0 | Opt out of the boot-lazy bundle deferral (either variant): build the renderer before bind (no SSR-only 404 window) at the cost of a slower first-accept. On by default when boot-lazy is active. |
ZFB_DEV_TIMING | 1 or true | Emit per-tick timing lines for dev-mode profiling. Off by default. |
Example
# Default — binds localhost:3000
zfb dev
# Custom port
zfb dev --port 8080
# Expose to the LAN (0.0.0.0)
zfb dev --host
# Explicit host
zfb dev --host 192.168.1.10 --port 4321zfb build
Build the project for production. Emits static HTML (and, when an adapter is configured, a server entry) to the output directory.
zfb build [--outdir <dir>] [--minify-html | --no-minify-html]
[--strict-broken | --no-strict-broken]
[--emit-render-artifacts | --no-emit-render-artifacts]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--outdir | path | config / dist | Output directory for the production build. Overrides config outDir; when both are absent, uses dist. |
--minify-html | boolean flag | config/default | Enable production HTML minification for this build. Overrides minifyHtml: false or an omitted config value. |
--no-minify-html | boolean flag | config/default | Disable production HTML minification for this build. Overrides minifyHtml: true from config or presets. |
--strict-broken | boolean flag | config/default | Fail this build (non-zero exit) when markdown link validation finds a broken link. Overrides strictBrokenLinks: false or an omitted config value. On a project with no linkValidation config at all, it force-enables link validation with its defaults rather than doing nothing. |
--no-strict-broken | boolean flag | config/default | Do not fail this build on broken links. Overrides strictBrokenLinks: true from config or presets. Disables the top-level strict overlay only — a linkValidation block that explicitly sets failOnBroken: true stays in effect. |
--emit-render-artifacts | boolean flag | config/default | Write a JSON render artifact under __zfb/render/ for every markdown/MDX-backed HTML route whose page renders exactly one top-level content region. Overrides emitRenderArtifacts: false or an omitted config value. |
--no-emit-render-artifacts | boolean flag | config/default | Do not write render artifacts for this build. Overrides emitRenderArtifacts: true from config or presets. |
HTML minification is off by default unless minifyHtml: true is set or --minify-html is passed. It runs inside zfb's Rust post-processing pipeline and does not spawn a Node.js minifier subprocess. The first version is conservative: rendered HTML pages are candidates, source .html passthrough pages remain verbatim, and non-HTML outputs are skipped.
Strict broken-link gating is likewise off by default. --strict-broken and --no-strict-broken conflict — passing both is an error — and either one overrides the strictBrokenLinks config field, which in turn overrides the false default. Both flags affect zfb build only; zfb dev never sees the strict overlay and keeps whatever link-validation behavior your own linkValidation config gives it. See Link validation — Strict mode for the full behavior, including how this differs from resolveMarkdownLinks.onBrokenLinks.
Render-artifact export is likewise off by default. --emit-render-artifacts and --no-emit-render-artifacts conflict — passing both is an error — and either one overrides the emitRenderArtifacts config field, which in turn overrides the false default. Both flags affect zfb build only; zfb dev never writes render artifacts. See Render artifacts for the full JSON contract, the region rule, and which routes participate.
Example
zfb build
zfb build --outdir public
zfb build --minify-html
zfb build --no-minify-html
zfb build --strict-broken
zfb build --no-strict-broken
zfb build --emit-render-artifacts
zfb build --no-emit-render-artifactszfb preview
Serve a previously built site locally. Run zfb build first.
zfb preview [--port <port>] [--host [<host>]] [--outdir <dir>]In static mode (no adapter, or adapter: "none"), zfb preview also runs the SAME two request-time layers zfb dev does, ahead of the plain static-file waterfall: a plugin's previewMiddleware registrations, then public/_redirects (see Static Assets — _redirects). A plugin claiming a URL always wins over a _redirects rule for that same URL.
Only GET/HEAD requests reach the _redirects matcher — mirrors real Workers Static Assets, which only ever probes the asset layer (and therefore _redirects) for those two methods. Any other method either reaches a plugin handler (which accepts every method) or 405s; a rule that would otherwise fire for GET /old-page never fires for POST /old-page.
_redirects and a custom `base`
Static preview serves whatever zfb build emitted under the selected output directory, and does not know about your project's base prefix — it has no prefix-stripping layer of its own. So _redirects rule sources are matched against the full request path, exactly as written in the file — they are never rewritten to add or strip a base prefix. zfb dev mirrors that matching shape even though its router is mounted under base. If zfb.config.ts sets base: "/pj/site/", a rule meant to redirect / must be written with the prefix included (/), not as /.
When zfb.config.json's adapter field names a supported adapter (e.g. @takazudo/zfb-adapter-cloudflare), zfb preview hands off to wrangler dev instead of zfb's own static server — the output directory selected from --outdir or config outDir then only pre-checks that the directory exists, since wrangler's own config controls what it actually serves. See SSR and Cloudflare Bindings.
Adapter preview runs a pre-flight pnpm exec wrangler --version check before handing off, treating zfb's tested baseline as the minimum supported version: an older wrangler aborts with upgrade guidance, while an equal or newer one proceeds (newer versions print an info line, or a warning on an untested major). Set ZFB_SKIP_WRANGLER_VERSION_CHECK=1 only as a temporary escape hatch (e.g. if wrangler's version output format changes).
Adapter mode: zfb serves nothing
Under adapter mode, wrangler dev serves the whole site — zfb never boots its own router, and no plugin host is spawned to check for previewMiddleware at all. _redirects still works there, but it's wrangler/Workers Static Assets honouring the file natively, not zfb's _redirects engine. A previewMiddleware registration has no wrangler equivalent and simply does not run under adapter-mode preview — if your project has plugins configured, zfb preview prints a one-time warning to that effect before handing off.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--port | u16 | 4321* | Port to bind the preview server to. |
--host | string | localhost* | Host interface to bind to. Bare --host (no value) is a shortcut for 0.0.0.0. |
--outdir | path | config / dist | Directory to serve the built artifacts from. Overrides config outDir; when both are absent, uses dist. |
* Falls back through: CLI flag → port/host in zfb.config.json → built-in default.
Output-directory precedence is likewise CLI --outdir → config outDir → built-in dist.
Example
zfb build && zfb preview
zfb preview --port 8080
zfb preview --host --outdir publiczfb check
Typecheck the project and validate content-collection frontmatter against their schemas.
zfb check [--skip-tsc]This command runs two checks:
TypeScript — invokes
tsc --noEmitas a subprocess. Catches type errors inzfb.config.ts, collection schemas, andsrc/.Collection schema validation — validates every collection entry's frontmatter against the JSON Schema declared in each collection's
schemafield inzfb.config.json/zfb.config.ts. The build itself does not validate frontmatter, so this is the enforcement gate.
Either failure mode exits with a non-zero code.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--skip-tsc | bool | false | Skip the tsc --noEmit subprocess. Schema validation still runs. Useful when TypeScript is not installed or for schema-only CI lanes. |
Example
# Full check — tsc + schema validation
zfb check
# Schema-only (skip tsc)
zfb check --skip-tscTip
zfb check is the recommended way to validate content collections in CI. Wire it after zfb build to catch frontmatter violations that the build silently ignores.
Environment variables
The following environment variables affect zfb globally (not specific to one subcommand):
| Variable | Default | Description |
|---|---|---|
ZFB_DEV_EAGER | unset | Set to 1 to disable lazy dev rendering in zfb dev. Re-renders every affected route eagerly on each file change. |
ZFB_LAZY_DEV_RENDER | unset | Set to 0 or 1 for precise control over the lazy dev-render switch. Takes precedence over ZFB_DEV_EAGER when both are set. |
ZFB_DEV_BOOT_LAZY | unset | Set to 1 to opt in to fast dev-server boot: serve a valid prebuilt dist/ immediately and defer each route's rendering to its first request (warns and falls back to the eager boot render when no servable dist/ exists), or set to cold for the seedless variant — same deferral, no dist/ required; a route with no fallback artifact serves the dev 404 page until its own first request, though a leftover dist/ from an unrelated prior build still serves stale bytes ahead of the 404 where present. Requires lazy rendering; no-op when ZFB_DEV_EAGER is set. |
ZFB_DEV_DEFER_BUNDLE | unset | Set to 0 to opt out of the boot-lazy bundle deferral (either variant; build the renderer before bind instead of after the first request). On by default when boot-lazy is active. |
ZFB_DEV_TIMING | unset | Set to 1 or true to print dev-mode phase timings for each file-change tick. |
ZFB_PLUGIN_HOOK_TIMEOUT | 120 (seconds) | Maximum time (in seconds) any single plugin hook reply is awaited before the plugin host is force-killed and the build fails. Precedence: pluginHookTimeoutSecs in config > this env var > 120s built-in default. |
ZFB_ESBUILD_BIN | unset | Absolute path to an esbuild binary. Used by the bundler, islands compiler, tests, and TypeScript config loading (zfb.config.ts) when an embedded/staged esbuild binary is unavailable or should be overridden. |
ZFB_TAILWIND_BIN | unset | Absolute path to a Tailwind CSS v4 binary. Overrides the embedded/staged Tailwind binary used by the CSS engine. An empty value is treated the same as unset, matching the ZFB_ESBUILD_BIN contract above. |
ZFB_DEBUG_SNAPSHOT | unset | Set to 1 or true to log the content snapshot's entry count and serialized byte size to stderr during zfb build, for monitoring V8 memory pressure on large sites. See Incremental Rebuild. |
ZFB_SKIP_WRANGLER_VERSION_CHECK | unset | Set to 1, true, or yes to bypass the zfb preview adapter-mode wrangler minimum-version gate. Intended for temporary release-engineering use. |
Note
ZFB_PLUGIN_HOOK_TIMEOUT can also be set via pluginHookTimeoutSecs in defineConfig. The config field takes precedence over the environment variable.