v0.1.0-dev.1259

Ziex

/

The full-stack web framework for Zig.

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

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
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
25k
FrameworkZiex
Requests/sec25,857
P50 Latency0.97 ms
P99 Latency7.57 ms
7.7k
FrameworkLeptos
Requests/sec7,659
P50 Latency3.01 ms
P99 Latency25.68 ms
5.4k
FrameworkDioxus
Requests/sec5,361
P50 Latency1.68 ms
P99 Latency44.40 ms
2.3k
FrameworkJetzig
Requests/sec2,268
P50 Latency9.33 ms
P99 Latency60.03 ms
1.4k
FrameworkSolidStart
Requests/sec1,448
P50 Latency17.30 ms
P99 Latency69.50 ms
262
FrameworkNext.js
Requests/sec262
P50 Latency107.00 ms
P99 Latency232.21 ms
Dockerized SSR endpoint hit with 3000 requests at 30 concurrent connections, averaged over 3 runs.
Ziex
18.3%
FrameworkZiex
Avg CPU18.3%
Peak CPU41.2%
Requests/sec25,857
34.4%
FrameworkLeptos
Avg CPU34.4%
Peak CPU87.0%
Requests/sec7,659
40.6%
FrameworkDioxus
Avg CPU40.6%
Peak CPU75.0%
Requests/sec5,361
57.9%
FrameworkSolidStart
Avg CPU57.9%
Peak CPU85.8%
Requests/sec1,448
63.5%
FrameworkJetzig
Avg CPU63.5%
Peak CPU98.6%
Requests/sec2,268
68.8%
FrameworkNext.js
Avg CPU68.8%
Peak CPU85.5%
Requests/sec262
Average CPU utilization during the load test via cgroup cpu.stat. Hover for peak CPU.
6.7 MB
FrameworkLeptos
Idle Memory1.7 MB
Peak Memory6.7 MB
9.1 MB
FrameworkDioxus
Idle Memory1.8 MB
Peak Memory9.1 MB
10.7 MB
FrameworkJetzig
Idle Memory6.4 MB
Peak Memory10.7 MB
Ziex
15.3 MB
FrameworkZiex
Idle Memory10.9 MB
Peak Memory15.3 MB
138.5 MB
FrameworkSolidStart
Idle Memory30.3 MB
Peak Memory138.5 MB
238.3 MB
FrameworkNext.js
Idle Memory77.9 MB
Peak Memory238.3 MB
Peak container RSS during load test. Lower is better. Hover for idle memory.
248 ms
FrameworkLeptos
Cold Start248 ms
Image Size19 MB
264 ms
FrameworkDioxus
Cold Start264 ms
Image Size94 MB
Ziex
282 ms
FrameworkZiex
Cold Start282 ms
Image Size14 MB
288 ms
FrameworkJetzig
Cold Start288 ms
Image Size62 MB
482 ms
FrameworkSolidStart
Cold Start482 ms
Image Size350 MB
897 ms
FrameworkNext.js
Cold Start897 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.4 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-05View 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 = .{ .seconds = 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 = data.?.id,
        .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