Layouts
Layouts wrap your pages with shared UI like navigation, headers, and footers. They nest automatically based on directory structure.
Basic Layout
Create a layout.rs file in any routes directory:
src/routes/layout.rs
use rejoice::{Children, Req, Res, html, DOCTYPE};
pub async fn layout(req: Req, res: Res, children: Children) -> Res {
res.html(html! {
(DOCTYPE)
html {
head {
meta charset="utf-8";
title { "My App" }
}
body {
nav {
a href="/" { "Home" }
a href="/about" { "About" }
}
main { (children) }
footer { "Built with Rejoice" }
}
}
})
}The children parameter contains the rendered page content.
Root Layout
A layout at src/routes/layout.rs wraps all pages in your app. This is where you typically put:
- The HTML document structure (
<!DOCTYPE html>,<html>,<head>,<body>) - Global navigation
- Meta tags
- Global styles
Nested Layouts
Layouts nest based on directory structure:
For /dashboard/settings:
settings.rsrenders the page contentdashboard/layout.rswraps it with dashboard UIlayout.rswraps everything with the document structure
Layout with State
If your app uses state, layouts receive it as the first parameter:
src/routes/layout.rs
use crate::AppState;
use rejoice::{Children, Req, Res, html, DOCTYPE};
pub async fn layout(state: AppState, req: Req, res: Res, children: Children) -> Res {
// Access state.db, state.config, etc.
res.html(html! {
(DOCTYPE)
html {
head { title { "My App" } }
body { (children) }
}
})
}Layout Bypass
Non-HTML responses automatically bypass layouts. This is useful for:
- Redirects
- API endpoints returning JSON
- File downloads
pub async fn get(req: Req, res: Res) -> Res {
let session = req.cookies.get("session_id");
if session.is_none() {
// This redirect bypasses all layouts
return res.redirect("/login");
}
// This HTML gets wrapped in layouts
res.html(html! {
h1 { "Dashboard" }
})
}Authentication Pattern
Use layouts to protect groups of routes:
src/routes/admin/layout.rs
// src/routes/admin/layout.rs
use crate::AppState;
use rejoice::{Children, Req, Res, html};
pub async fn layout(state: AppState, req: Req, res: Res, children: Children) -> Res {
let session = req.cookies.get("session_id");
// Check authentication
if !is_admin(&state, session).await {
return res.redirect("/login");
}
// Render admin layout
res.html(html! {
div class="admin-layout" {
aside { /* Admin sidebar */ }
main { (children) }
}
})
}All pages under src/routes/admin/ will be protected.
Next Steps
- HTML Templates — Learn Maud syntax
- Request Object — Read cookies and headers
- Response Object — Redirects and cookies