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.
The view engine is the core of Mizu’s template system. This guide covers all configuration options and how to use them effectively.
Creating an Engine
Create a view engine with view.New():
import "github.com/go-mizu/mizu/view"
engine := view.New(view.Config{
Dir: "./views",
Extension: ".html",
DefaultLayout: "default",
Development: true,
})
Configuration Options
Dir
The directory where your templates are stored.
view.Config{
Dir: "./views", // Default: "views"
}
The engine expects this structure inside the directory:
views/
├── layouts/ # Layout templates
│ └── default.html
└── pages/ # Page templates
├── home.html
└── about.html
An fs.FS filesystem to load templates from. Use this with embed.FS in production to bundle templates into your binary.
import "embed"
//go:embed views
var viewsFS embed.FS
engine := view.New(view.Config{
FS: viewsFS, // Use embedded filesystem
})
When FS is set, templates are loaded from the embedded filesystem instead of the local filesystem. The directory structure inside the embedded FS should match what you’d have on disk.
Extension
The file extension for template files.
view.Config{
Extension: ".html", // Default: ".html"
}
You can use other extensions:
view.Config{
Extension: ".tmpl", // Now looks for .tmpl files
}
DefaultLayout
The layout template to use when rendering pages. This refers to a file in the layouts/ directory.
view.Config{
DefaultLayout: "default", // Uses layouts/default.html
}
You can override this per-render with view.Layout() or disable it with view.NoLayout().
Delims
Custom template delimiters. Useful if your templates contain {{ and }} literally (common with Vue.js or Angular templates).
view.Config{
Delims: [2]string{"<%", "%>"}, // Default: ["{{", "}}"]
}
Now templates use <% %> instead:
<h1>Hello, <%.Data.Name%></h1>
Development
Enables development mode features:
- Template reload - Templates are re-read from disk on every request
- No caching - Templates are never cached
view.Config{
Development: true, // Default: false
}
Important: Never enable Development mode in production. It significantly impacts performance.
A common pattern is to use an environment variable:
view.Config{
Development: os.Getenv("ENV") != "production",
}
Funcs
Custom template functions to add to all templates.
import "html/template"
import "strings"
view.Config{
Funcs: template.FuncMap{
"uppercase": strings.ToUpper,
"formatDate": func(t time.Time) string {
return t.Format("Jan 2, 2006")
},
},
}
Now you can use these in templates:
<h1>{{uppercase .Data.Name}}</h1>
<p>Posted on {{formatDate .Data.CreatedAt}}</p>
Built-in Functions
The view engine provides these functions by default:
| Function | Description | Example |
|---|
dict | Create a map | {{dict "key" "value"}} |
list | Create a slice | {{list 1 2 3}} |
upper | Uppercase string | {{upper .Data.Name}} |
lower | Lowercase string | {{lower .Data.Name}} |
trim | Trim whitespace | {{trim .Data.Text}} |
contains | Check substring | {{if contains .Data.Text "hello"}} |
replace | Replace substring | {{replace .Data.Text "old" "new"}} |
split | Split string | {{split .Data.Tags ","}} |
join | Join slice | {{join .Data.Items ", "}} |
hasPrefix | Check prefix | {{if hasPrefix .Data.URL "https"}} |
hasSuffix | Check suffix | {{if hasSuffix .Data.File ".go"}} |
Engine Methods
New
Creates a new view engine with the given configuration.
engine := view.New(view.Config{
Dir: "./views",
Development: true,
})
Load
Loads and validates all templates at startup. Call this in production to fail fast if templates have errors.
engine := view.New(config)
if err := engine.Load(); err != nil {
log.Fatal("template error:", err)
}
This loads all files in layouts/ and pages/ directories.
Clear
Clears the template cache. Useful if you need to reload templates without restarting.
Render
Renders a page template. The first argument is an io.Writer (like http.ResponseWriter), followed by the template name, data, and optional render options.
err := engine.Render(w, "home", view.Data{
"Title": "Home",
"User": currentUser,
})
The template name refers to a file in pages/ without the extension.
Middleware
Returns a Mizu middleware that adds the engine to every request context.
app := mizu.New()
app.Use(engine.Middleware())
After adding this middleware, you can use view.From(c) and view.Render(c, ...) in handlers.
Package Functions
From
Retrieves the engine from a request context. Use this in handlers after adding the middleware.
func handler(c *mizu.Ctx) error {
engine := view.From(c)
if engine == nil {
return errors.New("view engine not configured")
}
return engine.Render(c.Writer(), "page", data)
}
Render (package-level)
A convenience function that gets the engine from context and renders. This is the most common way to render templates.
func handler(c *mizu.Ctx) error {
return view.Render(c, "home", view.Data{
"Title": "Home",
})
}
This is equivalent to:
engine := view.From(c)
c.Writer().Header().Set("Content-Type", "text/html; charset=utf-8")
c.Writer().WriteHeader(200)
return engine.Render(c.Writer(), "home", data)
Render Options
When calling Render(), you can pass options to customize behavior.
Layout
Override the default layout for this render:
view.Render(c, "dashboard", data, view.Layout("admin"))
This uses layouts/admin.html instead of the default layout.
NoLayout
Render without any layout (useful for partial responses):
view.Render(c, "user-row", data, view.NoLayout())
Template Data Structure
When you render a template, your data is wrapped in a structure:
type pageData struct {
Page pageMeta // Template metadata
Data any // Your data
Content template.HTML // Rendered page (only in layouts)
}
type pageMeta struct {
Name string // Template name (e.g., "home")
Layout string // Layout name (e.g., "default")
}
In page templates:
<h1>{{.Data.Title}}</h1>
<p>Template: {{.Page.Name}}</p>
In layout templates:
<title>{{.Data.Title}}</title>
<body>
{{.Content}} <!-- Rendered page content -->
</body>
Complete Example
Here’s a typical configuration for a production app:
package main
import (
"embed"
"html/template"
"os"
"strings"
"time"
"github.com/go-mizu/mizu"
"github.com/go-mizu/mizu/view"
)
//go:embed views
var viewsFS embed.FS
func main() {
isDev := os.Getenv("ENV") != "production"
config := view.Config{
Extension: ".html",
DefaultLayout: "default",
Development: isDev,
Funcs: template.FuncMap{
"formatDate": formatDate,
"truncate": truncate,
},
}
if isDev {
// Development: load from filesystem
config.Dir = "./views"
} else {
// Production: load from embedded FS
config.FS = viewsFS
}
engine := view.New(config)
// Preload templates in production to fail fast
if !isDev {
if err := engine.Load(); err != nil {
panic(err)
}
}
app := mizu.New()
app.Use(engine.Middleware())
app.Get("/", homeHandler)
app.Get("/about", aboutHandler)
app.Listen(":8080")
}
func formatDate(t time.Time) string {
return t.Format("January 2, 2006")
}
func truncate(s string, length int) string {
if len(s) <= length {
return s
}
return s[:length] + "..."
}
func homeHandler(c *mizu.Ctx) error {
return view.Render(c, "home", view.Data{
"Title": "Welcome",
})
}
func aboutHandler(c *mizu.Ctx) error {
return view.Render(c, "about", view.Data{
"Title": "About Us",
})
}
Error Handling
Template Errors
The engine returns an Error type that wraps template parsing and execution errors:
err := engine.Render(w, "page", data)
if err != nil {
var viewErr *view.Error
if errors.As(err, &viewErr) {
log.Printf("Template error (%s %q): %v",
viewErr.Kind, viewErr.Name, viewErr.Err)
}
}
The Error type has:
Kind - “page”, “layout”, or “template”
Name - Template name
Err - Underlying error (nil if not found)
ErrNotFound
Returned when a template file doesn’t exist:
if errors.Is(err, view.ErrNotFound) {
// Template file not found
}
Best Practices
1. Use Development Mode Locally
Always enable development mode during development:
Development: os.Getenv("ENV") != "production"
2. Load Templates in Production
Call Load() at startup to catch template errors early:
if err := engine.Load(); err != nil {
log.Fatal(err)
}
3. Embed Templates for Production
Use embed.FS so templates are bundled in your binary:
//go:embed views
var viewsFS embed.FS
This eliminates the need to deploy template files alongside your binary.
4. Keep Custom Functions Pure
Custom functions should be pure (no side effects):
// Good: pure function
"uppercase": strings.ToUpper,
// Bad: has side effects
"log": func(s string) string {
log.Println(s) // Side effect!
return s
},
5. Use Consistent Data Structures
Create struct types for complex template data:
type PageData struct {
Title string
User *User
Posts []Post
}
view.Render(c, "home", PageData{
Title: "Home",
User: currentUser,
Posts: posts,
})
Note: When using a struct instead of view.Data (which is map[string]any), access it directly in templates:
<h1>{{.Data.Title}}</h1> <!-- With view.Data map -->
<h1>{{.Data.Title}}</h1> <!-- With struct - same access pattern -->