Hello world — web

A Verve page is a plain Zig function that receives a per-request *verve.Context and returns a *verve.Node tree. The server renders it to HTML; the WASM client hydrates whatever is interactive.

1. Declare a route

Routes live in src/app/routes.zig — one comptime table the server matches at request time:

const verve = @import("verve");
const components = @import("components.zig");

pub const routes: []const verve.Route = &.{
    verve.Route.init("/", renderHome),
    verve.Route.init("/hello/:name", renderHello),
};

fn renderHome(ctx: *verve.Context) !*verve.Node {
    const body = try components.home(ctx);
    return components.page(ctx, body);
}

fn renderHello(ctx: *verve.Context) !*verve.Node {
    const name = ctx.param("name") orelse "world";
    const body = ctx.div().class("home").children(.{
        ctx.h1("Hello"),
        ctx.p().children(.{
            ctx.span().text("Greetings, "),
            ctx.code(name),
        }),
    }).build();
    return components.page(ctx, body);
}

ctx.param("name") reads the :name path segment captured by the router. Everything allocates from the request arena (ctx.alloc()), which is wiped when the response is sent — no manual frees.

2. Build the page tree

Element factories return *Node so chains read like the HTML they produce. .build() finalizes the chain and surfaces any allocation error that occurred along the way:

return ctx.div().class("card")
    .children(.{
        ctx.h1("Verve"),
        ctx.p().text("SSR first; islands hydrate the interactive parts."),
        ctx.a("/docs", "Read the docs"),
    })
    .build();

3. Run it

zig build run
# → http://127.0.0.1:8080

The build compiles your routes into the server binary, compiles the client runtime to client.wasm, and generates /api/<fn> endpoints for every function in app.Actions (see Server functions).

What you get for free

  • SSR with zero JS required — every page is fully formed HTML; forms built with ctx.actionForm work with JavaScript disabled.
  • Hydrationbind/z-on-* attributes wire DOM nodes to signals in the WASM client after load.
  • Typed server callsapp.Actions functions become /api/<fn> routes with comptime-generated client stubs.

Next: Hello world (desktop).