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.
This guide covers everything you need to deploy your Mizu views in production: embedding templates, caching, performance optimization, and best practices.
Embedding Templates
In production, embed templates into your binary. This eliminates file system dependencies and ensures templates canβt be accidentally modified.
Using embed.FS
Goβs embed package lets you include files in your binary:
package main
import (
"embed"
"github.com/go-mizu/mizu"
"github.com/go-mizu/mizu/view"
)
//go:embed views
var viewsFS embed.FS
func main() {
engine := view.New(view.Config{
FS: viewsFS, // Use embedded filesystem
Extension: ".html",
DefaultLayout: "default",
})
app := mizu.New()
app.Use(engine.Middleware())
// ... routes ...
app.Listen(":8080")
}
Directory Structure for Embedding
The //go:embed directive embeds a directory relative to the source file:
myapp/
βββ main.go # Contains //go:embed views
βββ views/
β βββ layouts/
β β βββ default.html
β βββ pages/
β βββ home.html
β βββ about.html
βββ go.mod
After building, the binary contains all templates.
Conditional Embedding
Use environment variables to switch between development and production:
package main
import (
"embed"
"os"
"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,
}
if isDev {
// Development: filesystem for hot reload
config.Dir = "./views"
} else {
// Production: embedded with caching
config.FS = viewsFS
}
engine := view.New(config)
// ... rest of application
}
Preloading Templates
In production, preload all templates at startup to:
- Fail fast - Catch template errors before serving requests
- Warm cache - Avoid first-request latency
- Validate - Ensure all templates parse correctly
engine := view.New(view.Config{
FS: viewsFS,
DefaultLayout: "default",
})
// Preload and validate all templates
if err := engine.Load(); err != nil {
log.Fatal("Template error:", err)
}
app := mizu.New()
app.Use(engine.Middleware())
Preload Errors
Load() catches errors like:
- Missing template files
- Template syntax errors
- Invalid template references
err := engine.Load()
if err != nil {
// err contains details about the failure
log.Fatal(err)
}
Template Caching
In production mode (Development: false), templates are cached:
- Parsed once - Templates are parsed at first use or Load()
- Reused - Same parsed template serves all requests
- No disk I/O - Templates read from memory
view.Config{
Development: false, // Enable caching (default)
}
Pre-compute in Handlers
Move complex logic to Go:
// Good: compute once in handler
func handler(c *mizu.Ctx) error {
posts := fetchPosts()
// Pre-compute derived data
featured := filterFeatured(posts)
byCategory := groupByCategory(posts)
return view.Render(c, "posts", view.Data{
"Posts": posts,
"Featured": featured,
"ByCategory": byCategory,
})
}
<!-- Simple template: just displays pre-computed data -->
{{range .Data.Featured}}
<article>{{.Title}}</article>
{{end}}
Minimize Template Complexity
Complex templates are slower. Keep them focused:
<!-- Avoid: deeply nested conditionals -->
{{if .Data.A}}
{{if .Data.B}}
{{if .Data.C}}
...
{{end}}
{{end}}
{{end}}
<!-- Better: compute in Go, pass simple flag -->
{{if .Data.ShowSpecialContent}}
...
{{end}}
Error Handling
Custom Error Pages
Create error page templates:
<!-- views/pages/404.html -->
<div class="error-page">
<h1>404</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="/">Go Home</a>
</div>
<!-- views/pages/500.html -->
<div class="error-page">
<h1>500</h1>
<p>Something went wrong on our end.</p>
<p>Please try again later.</p>
</div>
Error Handler
Set up a global error handler:
app := mizu.New()
app.ErrorHandler = func(c *mizu.Ctx, err error) {
code := 500
page := "500"
// Check for HTTP errors
var httpErr *mizu.Error
if errors.As(err, &httpErr) {
code = httpErr.Code
if code == 404 {
page = "404"
}
}
// Log server errors
if code >= 500 {
log.Printf("Server error: %v", err)
}
// Render error page
c.Status(code)
if renderErr := view.Render(c, page, view.Data{
"Title": "Error",
"Error": err,
}); renderErr != nil {
// Fallback if error page fails
c.Text("An error occurred")
}
}
Template Error Handling
Handle template rendering errors gracefully:
func handler(c *mizu.Ctx) error {
err := view.Render(c, "page", data)
if err != nil {
// Log the error
log.Printf("Template error: %v", err)
// Return generic error
return c.Status(500).Text("Internal error")
}
return nil
}
Security
Content Security Policy
Set appropriate headers:
app.Use(func(next mizu.Handler) mizu.Handler {
return func(c *mizu.Ctx) error {
c.Set("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
return next(c)
}
})
HTML Escaping
Go templates escape HTML by default. Only bypass escaping for trusted content:
<!-- Safe: auto-escaped -->
{{.Data.UserComment}}
<!-- Only for trusted content -->
{{.Data.TrustedHTML}}
import "html/template"
view.Data{
"TrustedHTML": template.HTML(trustedContent),
}
CSRF Tokens
Include CSRF tokens in forms:
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="{{.Data.CSRFToken}}">
<!-- form fields -->
</form>
Monitoring
Template Metrics
Track template rendering performance:
func handler(c *mizu.Ctx) error {
start := time.Now()
err := view.Render(c, "page", data)
duration := time.Since(start)
metrics.RecordTemplateRender("page", duration)
return err
}
Health Checks
Verify templates work in health checks:
app.Get("/health", func(c *mizu.Ctx) error {
// Try rendering a simple template
engine := view.From(c)
var buf bytes.Buffer
err := engine.Render(&buf, "health-check", nil, view.NoLayout())
if err != nil {
return c.Status(500).JSON(map[string]string{
"status": "unhealthy",
"error": err.Error(),
})
}
return c.JSON(map[string]string{
"status": "healthy",
})
})
Deployment Checklist
Before Deploying
Environment Variables
# Production
ENV=production
PORT=8080
# Development
ENV=development
PORT=8080
Docker Example
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o server .
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/server .
# Templates are embedded - no need to copy views/
ENV ENV=production
EXPOSE 8080
CMD ["./server"]
Complete Production Setup
package main
import (
"embed"
"errors"
"log"
"os"
"github.com/go-mizu/mizu"
"github.com/go-mizu/mizu/view"
)
//go:embed views
var viewsFS embed.FS
func main() {
// Configuration
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
isDev := os.Getenv("ENV") != "production"
// Create view engine
config := view.Config{
Extension: ".html",
DefaultLayout: "default",
Development: isDev,
}
if isDev {
config.Dir = "./views"
} else {
config.FS = viewsFS
}
engine := view.New(config)
// Preload in production
if !isDev {
if err := engine.Load(); err != nil {
log.Fatal("Template error:", err)
}
log.Println("Templates loaded successfully")
}
// Create app
app := mizu.New()
app.Use(engine.Middleware())
// Error handler
app.ErrorHandler = func(c *mizu.Ctx, err error) {
code := 500
page := "500"
var httpErr *mizu.Error
if errors.As(err, &httpErr) {
code = httpErr.Code
if code == 404 {
page = "404"
}
}
if code >= 500 {
log.Printf("Error: %v", err)
}
c.Status(code)
view.Render(c, page, view.Data{"Title": "Error"})
}
// Routes
app.Get("/", homeHandler)
app.Get("/about", aboutHandler)
// Start server
log.Printf("Server starting on :%s (dev=%v)", port, isDev)
if err := app.Listen(":" + port); err != nil {
log.Fatal(err)
}
}
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",
})
}
Whatβs Next?
Now that your views are ready for production, explore: