Skip to main content
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>&copy; 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:
  1. Renders pages/home.html with your data
  2. Puts the result in .Content
  3. 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:
FieldDescription
.DataYour data from the handler
.Page.NamePage template name
.Page.LayoutLayout name
.ContentRendered 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>&copy; {{.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",
}

Meta Tags

<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)
        }
    }
}