Fast Performance
Significantly faster at SSR than many other frameworks. Optimized for speed and low latency.
Full-stack web framework for Zig.
Compile-time safety, deterministic performance, absolute simplicity, and a delightful developer experience.
Everything you need to build fast, safe, and deployable web applications.
Significantly faster at SSR than many other frameworks. Optimized for speed and low latency.
Familiar plain HTML-style markup, with full access to Zig's control flow.
Just files in folders to create routes.
Drop a route.zig beside your pages. Same file-based routing for GET, POST, and more.
SSR by default. Opt into client rendering where interaction matters.
Use Zig if, for, while, and switch directly in your markup—no template DSL.
CLI, hot reload, and editor extensions for the best development experience.
Native binaries for servers; WASI modules for Cloudflare and Vercel. Pick the artifact that fits the host.
Explicit memory management of Zig, no hidden allocations.
View benchmark →
Each framework runs in a Docker container limited to 2 CPU cores and 2 GB RAM.
Ziex compiles to a single, statically linked binary or a WASI module, making it compatible with every major cloud provider and edge network.
Deploy as a self-contained binary on Linux, macOS, Windows andy many more platforms that Zig supports.
Deploy to the edge via WASI. Native bindings for Cloudflare and Vercel.
Generate a statically pre-rendered site at build time. Deploy to GitHub Pages, Netlify, S3, or any CDN.
Build for any target from any host. Easily cross-compile optimized binaries for x86_64, aarch64, and more.
Short, focused snippets that show how each feature feels in practice.
const ControlIfProps = struct { is_admin: bool };
pub fn ControlIf(
allocator: zx.Allocator,
props: ControlIfProps,
) zx.Component {
return (
<div @{allocator}>
{if (props.is_admin) (
<span>Admin</span>
) else (
<span>Member</span>
)}
</div>
);
}pub fn ControlIfOptional(allocator: zx.Allocator) zx.Component {
const maybe_name: ?[]const u8 = "Zig";
return (
<div @{allocator}>
{if (maybe_name) |name| (<span>{name}</span>)}
</div>
);
}pub fn ControlIfError(allocator: zx.Allocator) zx.Component {
const parsed =
std.fmt.parseInt(u32, "42", 10);
return (
<div @{allocator}>
{if (parsed) |value| (
<span>{value}</span>
) else |err| (
<span>{@errorName(err)}</span>
)}
</div>
);
}pub fn ControlWhile(allocator: zx.Allocator) zx.Component {
var i: usize = 0;
return (
<ul @{allocator}>
{while (i < 3) : (i += 1) (<li>Item {i + 1}</li>)}
</ul>
);
}pub fn ControlWhileOptional(allocator: zx.Allocator) zx.Component {
var maybe: ?u32 = 1;
return (
<ul @{allocator}>
{while (maybe) |value| : (maybe = if (value < 3) value + 1 else null) (
<li>{value}</li>
)}
</ul>
);
}pub fn ControlWhileError(allocator: zx.Allocator) zx.Component {
var n: u32 = 1;
return (
<div @{allocator}>
{while (parseOnce(&n)) |value| (
<span>{value}<br /></span>
) else |err| (
<span>{@errorName(err)}</span>
)}
</div>
);
}
fn parseOnce(n: *u32) !u32 {
if (n.* < 100) {
n.* += 1;
return n.*;
}
return error.Done;
}pub fn CacheComponent(
allocator: zx.Allocator,
) !zx.Component {
return (<Slow @{allocator} @caching="10s" />);
}
fn Slow(allocator: zx.Allocator) !zx.Component {
try std.Io
.sleep(zx.io(), .fromSeconds(2), .awake);
return (<div @{allocator}>Slow</div>);
}pub fn Page(
ctx: zx.PageContext,
) !zx.Component {
try std.Io
.sleep(zx.io(), .fromSeconds(2), .awake);
return (
<div @allocator={ctx.arena}>
<h1>Expensive Page</h1>
<p>I take too long to load.</p>
</div>
);
}
pub const options = zx.PageOptions{
.caching = .{ .ttl = .fromSeconds(300) },
};pub fn Page(
ctx: zx.PageContext,
) zx.Component {
return (
<div @allocator={ctx.arena}>
<h1>Home</h1>
</div>
);
}pub fn Layout(
ctx: zx.LayoutContext,
children: zx.Component,
) zx.Component {
return (
<html @allocator={ctx.arena}>
<body>
{children}
</body>
</html>
);
}const ButtonProps = struct { label: []const u8 };
pub fn Button(
ctx: *zx.ComponentCtx(ButtonProps),
) zx.Component {
return (
<button @allocator={ctx.allocator}>
Click {ctx.props.label}
</button>
);
}pub fn Fragment(
allocator: zx.Allocator,
) zx.Component {
return (
<fragment @{allocator}>
<span>One</span>
<>
<p>Two</p><p>Three</p>
</>
</fragment>
);
}pub fn SpreadProps(
allocator: zx.Allocator,
) zx.Component {
const attrs =
.{ .class = "btn", .id = "cta" };
const class = "primary";
return (
<section @{allocator}>
<button {..attrs}>Spreading</button>
<button {class}>Shorthand</button>
</section>
);
}pub fn DynamicAttr(
allocator: zx.Allocator,
) zx.Component {
const is_active = true;
const class = if (is_active) "active" else "idle";
const color = if (is_active) "green" else "red";
return (
<section @{allocator}>
<button class={class}>Go</button>
<button style=`color: {color};`>
Template String
</button>
</section>
);
}pub fn Page(ctx: zx.PageContext) zx.Component {
const id = ctx.request.params.get("id");
return (
<section @allocator={ctx.arena}>
<h1>User {id}</h1>
</section>
);
}pub fn PUT(ctx: zx.RouteContext) !void {
try ctx.response.json(.{ .status = "ok" }, .{});
}
pub fn POST(ctx: zx.RouteContext) !void {
const Data = struct { id: u32 };
const data =
try ctx.request.json(Data, .{});
try ctx.response.json(.{
.id = if (data) |d| d.id else 0,
.created = true,
}, .{});
}pub fn GET(ctx: zx.RouteContext) !void {
try ctx.socket.upgrade({});
}
pub fn Socket(ctx: zx.SocketContext) !void {
try ctx.socket.write(
try ctx.fmt("{s}", .{ctx.message}),
);
}
pub fn SocketOpen(ctx: zx.SocketOpenContext) !void {
try ctx.socket.write("Opened!");
}pub fn Page(ctx: zx.PageContext) !zx.Component {
return (
<main @allocator={ctx.arena}>
<Counter @rendering={.client} />
</main>
);
}
pub fn Counter(ctx: *zx.ComponentCtx(void)) zx.Component {
const count = ctx.state(i32, 0);
return (
<div @allocator={ctx.allocator} class="counter">
<button onclick={ctx.bind(increment)}>
Click Me: {count}
</button>
</div>
);
}
fn increment(e: *zx.client.Event.Stateful) void {
const count = e.state(i32);
count.set(count.get() + 1);
}