Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
Layouts are the HTML shell that wraps your pages. They contain the common structure shared across pages: doctype, head, navigation, footer, and more.
What is a Layout?
A layout is a template that defines the outer structure of your pages. Instead of repeating the HTML boilerplate in every page, you define it once in a layout.
Without layouts - every page repeats this:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<nav>...</nav>
<!-- Page content here -->
<footer>...</footer>
</body>
</html>
With layouts - define once, pages fill in the content:
<!DOCTYPE html>
<html>
<head>
<title>{{.Data.Title}}</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<nav>...</nav>
{{.Content}}
<footer>...</footer>
</body>
</html>
Directory Structure
Layouts live in the layouts/ subdirectory:
views/
βββ layouts/
β βββ default.html # Default layout
β βββ admin.html # Admin layout
β βββ minimal.html # Minimal layout
βββ pages/
βββ home.html
βββ dashboard.html
Creating a Layout
Basic Layout
<!-- views/layouts/default.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Data.Title}}</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
{{.Content}}
</main>
<footer>
<p>© 2024 My Company</p>
</footer>
<script src="/js/main.js"></script>
</body>
</html>
Key point: {{.Content}} is where the rendered page content is inserted.
Page Template
Pages simply provide content - no layout wrapper needed:
<!-- views/pages/home.html -->
<h1>Welcome, {{.Data.Name}}!</h1>
<p>This is the home page.</p>
How It Works
When you render a page:
view.Render(c, "home", view.Data{
"Title": "Home",
"Name": "Alice",
})
The engine:
- Renders
pages/home.html with your data
- Puts the result in
.Content
- Renders
layouts/default.html with .Content set
Setting the Default Layout
Configure the default layout in your engine:
engine := view.New(view.Config{
Dir: "./views",
DefaultLayout: "default", // Uses layouts/default.html
})
Multiple Layouts
Create different layouts for different sections of your site.
Admin Layout
<!-- views/layouts/admin.html -->
<!DOCTYPE html>
<html>
<head>
<title>Admin - {{.Data.Title}}</title>
<link rel="stylesheet" href="/css/admin.css">
</head>
<body class="admin">
<div class="admin-sidebar">
<nav>
<a href="/admin">Dashboard</a>
<a href="/admin/users">Users</a>
<a href="/admin/posts">Posts</a>
<a href="/admin/settings">Settings</a>
</nav>
</div>
<div class="admin-content">
<header>
<h1>{{.Data.PageTitle}}</h1>
</header>
<main>
{{.Content}}
</main>
</div>
</body>
</html>
Using a Different Layout
Override the default layout when rendering:
func dashboardHandler(c *mizu.Ctx) error {
return view.Render(c, "admin/dashboard", view.Data{
"Title": "Dashboard",
"PageTitle": "Admin Dashboard",
"Stats": stats,
}, view.Layout("admin"))
}
Minimal Layout
For simple pages that donβt need full navigation:
<!-- views/layouts/minimal.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{.Data.Title}}</title>
<style>
body {
font-family: sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
</style>
</head>
<body>
{{.Content}}
</body>
</html>
func loginHandler(c *mizu.Ctx) error {
return view.Render(c, "login", view.Data{
"Title": "Login",
}, view.Layout("minimal"))
}
No Layout
For AJAX responses, partial HTML, or API endpoints that return HTML fragments:
// Returns just the page content, no layout
view.Render(c, "user-row", data, view.NoLayout())
This is useful for:
- HTMX partial updates
- AJAX content loading
- Email templates (that define their own complete HTML)
Layout Data Access
Layouts have access to all the same data as pages:
| Field | Description |
|---|
.Data | Your data from the handler |
.Page.Name | Page template name |
.Page.Layout | Layout name |
.Content | Rendered page content |
view.Render(c, "home", view.Data{
"Title": "Home Page",
"CurrentUser": user,
"Year": time.Now().Year(),
})
<!-- views/layouts/default.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{.Data.Title}}</title>
</head>
<body>
<nav>
{{if .Data.CurrentUser}}
<span>Hello, {{.Data.CurrentUser.Name}}</span>
<a href="/logout">Logout</a>
{{else}}
<a href="/login">Login</a>
{{end}}
</nav>
<main>
{{.Content}}
</main>
<footer>
<p>© {{.Data.Year}} My Company</p>
</footer>
</body>
</html>
Common Patterns
Dynamic Navigation
Highlight the current page:
<!-- views/layouts/default.html -->
<nav>
<a href="/" class="{{if eq .Page.Name "home"}}active{{end}}">Home</a>
<a href="/about" class="{{if eq .Page.Name "about"}}active{{end}}">About</a>
<a href="/contact" class="{{if eq .Page.Name "contact"}}active{{end}}">Contact</a>
</nav>
Or pass the current path:
view.Data{
"CurrentPath": c.Path(),
}
<a href="/" class="{{if eq .Data.CurrentPath "/"}}active{{end}}">Home</a>
Flash Messages
Display flash messages from sessions:
{{if .Data.Flash}}
<div class="flash flash-{{.Data.Flash.Type}}">
{{.Data.Flash.Message}}
</div>
{{end}}
Page-Specific Body Classes
<body class="{{.Data.BodyClass}}">
view.Data{
"BodyClass": "page-home dark-mode",
}
<head>
<title>{{.Data.Title}}</title>
<meta name="description" content="{{.Data.Description}}">
{{if .Data.NoIndex}}<meta name="robots" content="noindex">{{end}}
</head>
Conditional Scripts
<body>
{{.Content}}
<script src="/js/main.js"></script>
{{if .Data.NeedsChart}}
<script src="/js/chart.js"></script>
{{end}}
</body>
Best Practices
1. Keep Layouts Focused
Each layout should serve a clear purpose:
default.html - Main public pages
admin.html - Admin panel
auth.html - Login/signup pages
email.html - Email templates
2. Pass Common Data via Middleware
Instead of passing the same data in every handler, use middleware:
func commonDataMiddleware(next mizu.Handler) mizu.Handler {
return func(c *mizu.Ctx) error {
c.Locals("Year", time.Now().Year())
c.Locals("CurrentPath", c.Path())
return next(c)
}
}
3. Use Meaningful Names
// Good: clear purpose
view.Layout("admin")
view.Layout("email")
// Avoid: unclear
view.Layout("layout2")
view.Layout("other")
4. Donβt Overcomplicate
Start simple. Most sites only need 2-3 layouts:
- Main layout for public pages
- Admin layout for backend
- Minimal layout for auth pages
5. Test All Layouts
Make sure every layout works with your data:
func TestLayouts(t *testing.T) {
engine := view.New(config)
layouts := []string{"default", "admin", "minimal"}
for _, layout := range layouts {
var buf bytes.Buffer
err := engine.Render(&buf, "test-page", testData, view.Layout(layout))
if err != nil {
t.Errorf("Layout %s failed: %v", layout, err)
}
}
}