Full-stack Zig web framework (v0.5.0). Pure-Zig, zero deps, SSR with fine-grained reactivity, per-island WASM code splitting, a GSAP-class animation engine, an in-house WebGL2 3D renderer, and single-binary deploy.
Problem
Most web frameworks force a trade between time-to-first-byte (SSR), interactive feel (client reactivity), and operational shape (one binary versus a Node/Bun/Deno toolchain). Pick two, live with the third. The Rust side of that trade buys correctness at the cost of compile times and macro syntax. The Node side buys ergonomics at the cost of a runtime in front of your runtime. I wanted a third option: server-rendered, reactive, and shipped as a single binary the way Go services already ship.
Shape
A pure-Zig framework with zero third-party dependencies. SSR happens through a *Node tree streamed to the socket via std.http.Server. The same Signal / Effect / Owner / Resource graph that drives server-side reactivity ships into a wasm32-freestanding client runtime — DOM updates are a consequence of Signal.set, not a parallel write path. Per-island WASM chunks share linear memory with the main client.wasm, so adding islands is roughly free. The whole thing — framework, scaffolder, server-fn codegen, island chunker — lives in one repo behind one zig fmt rule set.
motion + gl
v0.5.0 folded two engines into the framework, both pure Zig compiled to wasm. verve.anim is a GSAP-class timeline — tweens, keyframes, stagger, ScrollTrigger pinning and scrubbing, MorphSVG, MotionPath, SplitText — authored server-side as a frozen descriptor and driven client-side by a small interpreter in the bridge. verve.gl is a from-scratch 3D renderer: scene graph, PBR materials, image-based lighting, BVH ray-picking, and a .vmesh/.venv asset format, all in Zig; the browser side is one dumb WebGL2 interpreter walking a binary command stream. The two compose — an anim timeline can scrub a gl camera's yaw against the scrollbar. The /gl scene on this site is that path end to end: procedural geometry baked into a ~5 KB .vmesh at build time, prefiltered IBL, and a scroll-scrubbed turntable.
Build
build.zig does the heavy lifting at configure time. It walks app.Actions to emit typed server-fn client stubs, parses src/app/islands.zig to discover islands, compiles one WASM chunk per island, and embeds a public-asset manifest if -Dpublic-dir= is passed. Routes are comptime-parsed once into a []const Segment slice; the runtime matcher walks the slice without allocating. Suspense uses Renderer.streamRender to flush the shell first and drain parked boundaries as <template> + verveSwap(N) chunks. CSRF is HMAC-SHA256, auto-issued cookie + __csrf form field, SameSite=Strict. CSP nonce is a per-request 12-byte hex value stamped onto every <script> and <style> the renderer emits.
Islands ship as <verve-island data-name=… data-props=…> markers. The bridge fetches each chunk on first encounter, caches the instantiation, copies props through a shared scratch region, and calls hydrate(ptr, len, root_id). Chunks import their memory from the main client (env.memory), which drops per-chunk size to ~73 bytes versus ~180 bytes standalone.
Result
This site runs on Verve. The case study you are reading was rendered by std.http.Server, served by a ~3.6 MB binary — the in-house 3D and animation engines now ride inside it — and shipped without npm install, cargo build, or a separate frontend toolchain. The /verve route demos the live reactive features — islands, SSE, ActionForm + CSRF, Suspense; /gl demos the renderer. The codebase carries 731 tests across the core, server, client, anim, and gl layers, a showcase example exercising every public export, and prebuilt releases on four platforms. Releases live at github.com/sirhco/verve.
stack
Zig 0.16std.http.Serverwasm32-freestandingWebGL2no third-party deps