Routing
Routes are one comptime table in src/app/routes.zig. The server matches the request path against it and renders the matched chain outside-in.
pub const routes: []const verve.Route = &.{
verve.Route.init("/", renderHome),
verve.Route.init("/work/:slug", renderWork),
verve.Route.layout("/app", renderShell, &.{
verve.Route.init("/dashboard", renderDashboard),
verve.Route.init("/settings/:section", renderSettings),
}),
verve.Route.init("/admin", renderAdmin).protect(adminGuard),
};Path parameters
:name segments capture into ctx.params; a trailing *rest wildcard captures the remainder:
fn renderWork(ctx: *verve.Context) !*verve.Node {
const slug = ctx.param("slug") orelse "";
...
}Captured slices point into the request path buffer — dupe into ctx.alloc() if you need them beyond the render.
Nested layouts
Route.layout(path, render, children) declares a layout whose children match relative paths beneath it. The server renders the child first, hands its tree to the layout via ctx.outlet():
fn renderShell(ctx: *verve.Context) !*verve.Node {
return ctx.main_().children(.{
ctx.nav().children(.{ ... }),
ctx.section().children(.{ctx.outlet()}),
}).build();
}When the layout path itself is requested (/app), no child matched and ctx.outlet_node is null — check it to render an index. Nesting depth is capped at 8.
Guards
.protect(fn) runs before render. Return null to allow, or a Redirect to bounce:
fn adminGuard(ctx: *verve.Context) ?verve.Redirect {
const meta = ctx.request_meta orelse return .{ .to = "/login" };
if (sessionValid(meta)) return null;
return .{ .to = "/login" };
}Guards on a layout protect every child beneath it.
Redirects from render
A handler can abort into a redirect — useful after form posts:
return ctx.redirect("/thanks"); // 303 See Other
return ctx.redirectWithStatus("/moved", 301); // explicit statusActive-link highlighting
ctx.el("a").href("/docs")
.attrFmt("class", "nav-link {s}", .{ctx.activeClass("/docs", "active")})activeClass compares against the current location (trailing slash normalized) — no prop drilling of the current path.
Client-side links
verve.link(ctx, "/counter", "Counter", .{ .prefetch_on_hover = true }) builds an anchor the client runtime can intercept and prefetch.
Next: Control flow.