Ziex

/

Full-stack web framework for Zig.

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

v0.1.0-rc.4Zig 0.17.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.

Performance

Fast Performance

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

Syntax

Familiar Syntax

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

Control Flow

Control Flow

Use Zig if, for, while, and switch directly in your markup—no template DSL.

Performance

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

Ziex
35k
FrameworkZiex
Requests/sec35,732
P50 Latency0.67 ms
P99 Latency3.73 ms
8.8k
FrameworkLeptos
Requests/sec8,778
P50 Latency2.90 ms
P99 Latency9.51 ms
5.6k
FrameworkDioxus
Requests/sec5,622
P50 Latency1.56 ms
P99 Latency44.06 ms
2.4k
FrameworkJetzig
Requests/sec2,378
P50 Latency11.55 ms
P99 Latency36.22 ms
1.5k
FrameworkSolidStart
Requests/sec1,516
P50 Latency16.58 ms
P99 Latency70.29 ms
267
FrameworkNext.js
Requests/sec267
P50 Latency102.28 ms
P99 Latency282.14 ms
Dockerized SSR endpoint hit with 3000 requests at 30 concurrent connections, averaged over 3 runs.View CI run
Ziex
15.8%
FrameworkZiex
Avg CPU15.8%
Peak CPU32.8%
Requests/sec35,732
35.8%
FrameworkLeptos
Avg CPU35.8%
Peak CPU69.7%
Requests/sec8,778
41.0%
FrameworkDioxus
Avg CPU41.0%
Peak CPU74.8%
Requests/sec5,622
58.8%
FrameworkSolidStart
Avg CPU58.8%
Peak CPU84.1%
Requests/sec1,516
63.4%
FrameworkJetzig
Avg CPU63.4%
Peak CPU91.8%
Requests/sec2,378
68.8%
FrameworkNext.js
Avg CPU68.8%
Peak CPU89.4%
Requests/sec267
Average CPU utilization during the load test via cgroup cpu.stat. Hover for peak CPU.View CI run
Ziex
6.7 MB
FrameworkZiex
Idle Memory1.9 MB
Peak Memory6.7 MB
6.7 MB
FrameworkLeptos
Idle Memory1.7 MB
Peak Memory6.7 MB
6.7 MB
FrameworkDioxus
Idle Memory1.9 MB
Peak Memory6.7 MB
7.2 MB
FrameworkJetzig
Idle Memory3.5 MB
Peak Memory7.2 MB
136.2 MB
FrameworkSolidStart
Idle Memory31.0 MB
Peak Memory136.2 MB
252.1 MB
FrameworkNext.js
Idle Memory77.9 MB
Peak Memory252.1 MB
Peak container RSS during load test. Lower is better. Hover for idle memory.View CI run
243 ms
FrameworkLeptos
Cold Start243 ms
Image Size19 MB
260 ms
FrameworkDioxus
Cold Start260 ms
Image Size94 MB
Ziex
287 ms
FrameworkZiex
Cold Start287 ms
Image Size14 MB
299 ms
FrameworkJetzig
Cold Start299 ms
Image Size62 MB
478 ms
FrameworkSolidStart
Cold Start478 ms
Image Size351 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.View CI run
Ziex
14 MB
FrameworkZiex
Image Size14 MB
Binary Size6.2 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
351 MB
FrameworkSolidStart
Image Size351 MB
Binary SizeN/A
Size of the final Docker image. Smaller images are faster to deploy and use less storage.View CI run

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
routes/api/route.zig
pub fn GET(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 Page(ctx: zx.PageContext) !zx.Component {
    return (
        <main @allocator={ctx.arena}>
            <Counter @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);
}
Preview