Why Rust
The bet behind writing a static-site framework in Rust, what it buys, and what it costs.
zfb is a static-site framework whose users write TypeScript and JSX. The framework itself is Rust. This page explains why.
Performance
The headline is per-page rebuild time measured in milliseconds. Parsing, type checking, content processing, and atomic file writes are all native Rust — not “fast enough”, just genuinely fast. A 2,000-page site rebuilds the affected pages in well under a second when a shared header changes, because the work is a tight Rust loop over a known dependency graph.
The dev loop is the same shape. A save produces an FS event; the watcher debounces; the orchestrator queries the graph; affected pages re-render; the dev server broadcasts a reload. End-to-end latency lives in the tens of milliseconds for a typical edit. The browser refresh is the slow part.
This is not magic. It is what you get when the framework does not JIT-warm an interpreter every save and does not keep a multi-gigabyte module graph in memory.
Memory safety and structured errors
Rust’s compiler eliminates use-after-free, data races, and null derefs at compile time. The build does not segfault halfway through writing dist/. The watcher does not panic when save bursts overlap. Boring wins, the kind you stop noticing once you have them.
The visible win is error quality. zfb uses anyhow end-to-end, so every error reaches the CLI as a chain: the immediate failure, the operation that failed, the file path, the higher-level intent. A TSX compile error shows the file, line, column, and the operation that triggered it — not a generic “build failed”.
Concurrency that fits
The build orchestrator schedules across CPUs; the dev server runs an axum listener and an SSE broadcast at the same time. Rayon handles parallel-page rendering; tokio handles the async I/O. They coexist without ceremony — Rust’s type system enforces the boundaries, so threads never share state they should not share.
The same code in Node would need worker threads or child processes for equivalent isolation. Workers do not share modules; child processes do not share memory. Either way, you pay coordination overhead on every page that the Rust version does for free.
Distribution
zfb is a single binary distributed via npm. @takazudo/zfb pulls a prebuilt platform binary via npm optional-deps — that executable is the framework. No node_modules for zfb itself, no transitive tree to audit. Your project still brings its own pnpm for the dependencies you import (TypeScript, npm packages, lockfiles), but the framework is not in that tree. (Contributors who need to build from source can follow the steps in From source.)
A single binary pins cleanly. CI installs the version it needs; production deploys ship the same binary that ran in dev. The version your team is on is one number, not a constellation of package.json ranges.
Honest counterpoint: build cost
The first cargo build of zfb’s workspace is slow. The V8 source bundle (pulled in by zfb-render via deno_core) compiles for 15 to 30 minutes on a typical contributor machine. Incremental builds are fast; the first one hurts. This cost is accepted because Tauri distribution requires a single binary with no Node dependency on end-user machines — embedding deno_core is the only way to meet that constraint. See JS runtime for the full rationale.
zfb gates the V8 build behind a default-on embed_v8 cargo feature on zfb-render. Crates that do not need JavaScript rendering never pull in V8. A contributor running cargo test -p zfb-content compiles in seconds. End users do not pay this cost — they install the prebuilt binary.
The gate is not a free lunch on the linked zfb binary itself: V8 dominates the artifact (the bundled v8 rlib is ~169 MiB on its own, and the rest of the deno_* tree adds another ~13 MiB), so a V8-off zfb is roughly 82 MiB smaller stripped than the V8-on shipping binary. Today that lighter binary is only useful for V8-less consumers of the zfb-render library — there is no V8-less zfb build step yet, because SSG rendering needs V8 to run user pages. The feature is wired so a future shipping path (Tauri sidecar, standalone SSR server, cargo install-as-deploy) can opt into the smaller artifact without re-architecting the workspace.
Compared to JS-based alternatives
Vite, Next, Astro, SvelteKit — all great tools. zfb’s bet is different, not better in every direction: what if the framework was Rust and only the user code was JS / TS?
That tilts the trade-offs. Build speed gets cheaper because there is no bundler walking a JS module graph at framework cost. Distribution gets simpler — no framework node_modules. Memory and concurrency get language-level guarantees. Error quality gets a uniform shape through anyhow.
The cost is a smaller plugin ecosystem in the framework’s native language and a longer first build for contributors. zfb pays both on purpose, because the design point is users who want their build out of their way.
If you want a JS-native framework with a vast plugin ecosystem and a hot-reload story polished over years, those tools exist. If you want the framework to disappear into a fast binary and the rebuild to feel like nothing happened, that is what zfb is for.