Ziex
Build fast full-stack web apps with Zig.
JSX like syntax or just like HTML with having access to Zig's control flow. No JavaScript runtime, no garbage collection.
Features
Performance-focused web apps built with Zig.
It's Fast
~100x faster SSR than Next.js. Compiles to native code instead of running JavaScript.
Compile-Time Safety
Zig's type system catches bugs at compile time. No runtime surprises, no GC.
Familiar Syntax
Familiar JSX like syntax or just like HTML with having access to Zig's control flow.
Server-Side Rendering
Server-side rendering by default. Pages render on the server with zero configuration.
Static Site Generation
Static site generation by default. Pages render on the server with zero configuration.
File System Routing
Folder structure defines routes. No configs, no magic strings, just files in folders.
Client-side Rendering
Optional client-side rendering for interactive experiences when you need it.
Control Flow in Zig's Syntax
if/else, for/while, switch, standard control flow works as expected. It's just Zig.
Developer Tooling
CLI, hot reload, and editor extensions for the best DX.
Feature Examples
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={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={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={allocator}>
{if (parsed) |value| (
<span>{value}</span>
) else |err| (
<span>{err}</span>
)}
</div>
);
}pub fn ControlWhile(allocator: zx.Allocator) zx.Component {
var i: usize = 0;
return (
<ul @allocator={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={allocator}>
{while (maybe) |value| : (maybe = if (value < 3) value + 1 else null) (
<li>{value}</li>
)}
</ul>
);
}pub fn ControlWhileError(allocator: zx.Allocator) zx.Component {
return (
<div @allocator={allocator}>
{while (std.fmt.parseInt(u32, "42", 10)) |value| (
<span>{value}</span>
) else |err| (
<span>{err}</span>
)}
</div>
);
}pub fn CacheComponent(alloc: zx.Allocator) zx.Component {
return (<Expensive @allocator={alloc} @caching="10s" />);
}
fn Expensive(allocator: zx.Allocator) zx.Component {
std.Thread.sleep(std.time.ns_per_s * 3);
return (<div @allocator={allocator}></div>);
}pub fn Page(
ctx: zx.PageContext,
) zx.Component {
std.Thread.sleep(std.time.ns_per_s * 3);
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 },
};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}>
{ctx.props.label}
</button>
);
}pub fn Fragment(
allocator: zx.Allocator,
) zx.Component {
return (
<fragment @allocator={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={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={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.getParam("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 = data.?.id,
.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!");
}Familiar Syntax
Familiar JSX like syntax or just like HTML with having access to Zig's control flow.
pub fn QuickExample(allocator: zx.Allocator) zx.Component {
const is_loading = true;
const chars = "Hello, ZX Dev!";
var i: usize = 0;
return (
<main @allocator={allocator}>
<section>
{if (is_loading) (<h1>Loading...</h1>) else (<h1>Loaded</h1>)}
</section>
<section>
{for (chars) |char| (<span>{char}</span>)}
</section>
<section>
{for (users) |user| (
<Profile name={user.name} age={user.age} role={user.role} />
)}
</section>
<section>
{while (i < 10) : (i += 1) (<p>{i}</p>)}
</section>
</main>
);
}
fn Profile(allocator: zx.Allocator, user: User) zx.Component {
return (
<div @allocator={allocator}>
<h1>{user.name}</h1>
<p>{user.age}</p>
{switch (user.role) {
.admin => (<p>Admin</p>),
.member => (<p>Member</p>),
}}
</div>
);
}
const UserRole = enum { admin, member };
const User = struct { name: []const u8, age: u32, role: UserRole };
const users = [_]User{
.{ .name = "John", .age = 20, .role = .admin },
.{ .name = "Jane", .age = 21, .role = .member },
};
const zx = @import("zx");