Ziex

/

Full-stack web framework for Zig.

Compile-time safety, deterministic performance, absolute simplicity, and a delightful developer experience.

v0.1.0-dev.1259Zig 0.16.0
curl -fsSL https://ziex.dev/install | bash
zx init
powershell -c "irm ziex.dev/install.ps1 | iex"
zx init
npm init ziex
bun create ziex
git clone https://github.com/ziex-dev/template-starter my-app
cd my-app && zig build dev
Ziguana Mascot

Features

Everything you need to build fast, safe, and deployable web applications.

Fast Performance

Significantly faster at SSR than many other frameworks. Optimized for speed and low latency.

Familiar Syntax

Familiar plain HTML-style markup, with full access to Zig's control flow.

File System Routing

Just files in folders to create routes.

API Routes

Create API endpoints by adding route.zig files to your project.

Hybrid Rendering

Client-side rendering for interactive experiences when you need it.

Control Flow

if/else, for/while, and switch all work as expected. It's just Zig syntax.

Developer Tooling

CLI, hot reload, and editor extensions for the best development experience.

Deploy Anywhere

Standalone binaries, Cloudflare, Vercel, or any edge runtime host. Build once, run anywhere.

Predictable Memory

Explicit memory management of Zig, no hidden allocations.

Performance

View benchmark →
Each framework runs in a Docker container limited to 2 CPU cores and 2 GB RAM.

Ziex
48k
FrameworkZiex
Requests/sec48,234
P50 Latency0.53 ms
P99 Latency2.00 ms
11k
FrameworkLeptos
Requests/sec11,188
P50 Latency2.28 ms
P99 Latency9.70 ms
6.8k
FrameworkDioxus
Requests/sec6,804
P50 Latency1.41 ms
P99 Latency42.95 ms
2.8k
FrameworkJetzig
Requests/sec2,823
P50 Latency8.39 ms
P99 Latency44.55 ms
1.4k
FrameworkSolidStart
Requests/sec1,364
P50 Latency18.01 ms
P99 Latency77.74 ms
247
FrameworkNext.js
Requests/sec247
P50 Latency114.79 ms
P99 Latency273.97 ms
Dockerized SSR endpoint hit with 3000 requests at 30 concurrent connections, averaged over 3 runs.
Ziex
15.2%
FrameworkZiex
Avg CPU15.2%
Peak CPU28.0%
Requests/sec48,234
32.1%
FrameworkLeptos
Avg CPU32.1%
Peak CPU79.1%
Requests/sec11,188
39.1%
FrameworkDioxus
Avg CPU39.1%
Peak CPU91.3%
Requests/sec6,804
57.6%
FrameworkJetzig
Avg CPU57.6%
Peak CPU96.7%
Requests/sec2,823
58.5%
FrameworkSolidStart
Avg CPU58.5%
Peak CPU87.9%
Requests/sec1,364
69.8%
FrameworkNext.js
Avg CPU69.8%
Peak CPU90.7%
Requests/sec247
Average CPU utilization during the load test via cgroup cpu.stat. Hover for peak CPU.
7.3 MB
FrameworkDioxus
Idle Memory2.0 MB
Peak Memory7.3 MB
7.6 MB
FrameworkLeptos
Idle Memory1.6 MB
Peak Memory7.6 MB
11.9 MB
FrameworkJetzig
Idle Memory6.4 MB
Peak Memory11.9 MB
Ziex
16.9 MB
FrameworkZiex
Idle Memory10.8 MB
Peak Memory16.9 MB
132.7 MB
FrameworkSolidStart
Idle Memory29.8 MB
Peak Memory132.7 MB
233.0 MB
FrameworkNext.js
Idle Memory76.5 MB
Peak Memory233.0 MB
Peak container RSS during load test. Lower is better. Hover for idle memory.
271 ms
FrameworkDioxus
Cold Start271 ms
Image Size94 MB
277 ms
FrameworkLeptos
Cold Start277 ms
Image Size19 MB
294 ms
FrameworkJetzig
Cold Start294 ms
Image Size62 MB
Ziex
297 ms
FrameworkZiex
Cold Start297 ms
Image Size14 MB
441 ms
FrameworkSolidStart
Cold Start441 ms
Image Size350 MB
863 ms
FrameworkNext.js
Cold Start863 ms
Image Size275 MB
Time for the container to start and become ready to serve requests. Lower is better.
Ziex
14 MB
FrameworkZiex
Image Size14 MB
Binary Size6.1 MB
19 MB
FrameworkLeptos
Image Size19 MB
Binary Size10.9 MB
62 MB
FrameworkJetzig
Image Size62 MB
Binary Size54.4 MB
94 MB
FrameworkDioxus
Image Size94 MB
Binary Size9.5 MB
275 MB
FrameworkNext.js
Image Size275 MB
Binary SizeN/A
350 MB
FrameworkSolidStart
Image Size350 MB
Binary SizeN/A
Size of the final Docker image. Smaller images are faster to deploy and use less storage.
Last benchmarked on 2026-07-25View CI run →

Deploy Anywhere.

Ziex compiles to a single, statically linked binary or a WASI module, making it compatible with every major cloud provider and edge network.

Standalone

Deploy as a self-contained binary on Linux, macOS, Windows andy many more platforms that Zig supports.

Edge Anywhere

Deploy to the edge via WASI. Native bindings for Cloudflare and Vercel.

Static

Generate a statically pre-rendered site at build time. Deploy to GitHub Pages, Netlify, S3, or any CDN.

Cross-Platform

Build for any target from any host. Easily cross-compile optimized binaries for x86_64, aarch64, and more.

Code Examples

Short, focused snippets that show how each feature feels in practice.

Control Flow
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;
}
Caching
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) },
};
File System Routing
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>
    );
}
Components
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>
    );
}
Dynamic Path
pages/[id]/page.zx
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>
    );
}
API Route
route.zig
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,
    }, .{});
}
WebSocket
routes/ws/route.zig
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!");
}
Client-side Rendering
counter.zx
pub fn Client(allocator: zx.Allocator) zx.Component {
    return (<Counter @{allocator} @rendering={.client} />);
}
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);
}
Preview