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 frontend middleware offers extensive configuration options to handle various deployment scenarios, frameworks, and requirements.
Basic Configuration
Minimal Setup
The simplest configuration:
app.Use(frontend.New("./dist"))
This uses sensible defaults:
- Auto-detects mode based on
MIZU_ENV
- Serves from
./dist in production
- Proxies to
http://localhost:5173 in development
- Ignores
/api, /health, /metrics
With Dev Server
Specify both production and development:
app.Use(frontend.WithOptions(frontend.Options{
Mode: frontend.ModeAuto,
Root: "./dist",
DevServer: "http://localhost:5173",
}))
Embedded Filesystem
Use embedded files for single-binary deployment:
//go:embed all:dist
var distFS embed.FS
func setup() {
dist, _ := fs.Sub(distFS, "dist")
app.Use(frontend.WithFS(dist))
}
Options Reference
Controls how the middleware operates.
type Mode string
const (
ModeDev Mode = "dev" // Always proxy to dev server
ModeProduction Mode = "production" // Always serve static files
ModeAuto Mode = "auto" // Auto-detect from env
)
Usage:
app.Use(frontend.WithOptions(frontend.Options{
Mode: frontend.ModeAuto, // Default
}))
Auto-detection logic:
Environment variable checked in order:
MIZU_ENV
GO_ENV
ENV
If value is "production" or "prod" (case-insensitive) → Production mode
Otherwise → Development mode
Directory containing built frontend files (production only).
app.Use(frontend.WithOptions(frontend.Options{
Root: "./dist", // Default
}))
Path resolution:
- Relative paths are relative to working directory
- Absolute paths work as expected
Common paths:
- Vite:
"./dist"
- Angular:
"./dist/my-app/browser"
- Next.js:
"./out"
- Nuxt:
"./dist"
FS (Embedded Filesystem)
Embedded filesystem for production builds (takes precedence over Root).
//go:embed all:dist
var distFS embed.FS
func setup() {
dist, _ := fs.Sub(distFS, "dist")
app.Use(frontend.WithOptions(frontend.Options{
FS: dist, // Use embedded files
}))
}
Why use fs.Sub?
The embed directive includes the directory name:
distFS contains:
dist/
index.html
assets/
Using fs.Sub extracts the subdirectory:
dist contains:
index.html
assets/
Now index.html is at the root level as expected.
The fallback HTML file for SPA routing.
app.Use(frontend.WithOptions(frontend.Options{
Index: "index.html", // Default
}))
When is it used?
- Request path is
/ or empty
- Requested file doesn’t exist (SPA fallback)
- Requested path is a directory
DevServer
URL of the frontend development server.
app.Use(frontend.WithOptions(frontend.Options{
DevServer: "http://localhost:5173", // Default (Vite)
}))
Common dev server URLs:
- Vite:
"http://localhost:5173"
- Angular:
"http://localhost:4200"
- Next.js:
"http://localhost:3000"
- Create React App:
"http://localhost:3000"
DevServerTimeout
Timeout for requests to the dev server.
app.Use(frontend.WithOptions(frontend.Options{
DevServerTimeout: 30 * time.Second, // Default
}))
Increase for slow dev servers:
app.Use(frontend.WithOptions(frontend.Options{
DevServerTimeout: 60 * time.Second,
}))
ProxyWebSocket
Enable WebSocket proxying for HMR.
app.Use(frontend.WithOptions(frontend.Options{
ProxyWebSocket: true, // Default
}))
Disable if you don’t need HMR:
app.Use(frontend.WithOptions(frontend.Options{
ProxyWebSocket: false,
}))
URL prefix for serving the frontend.
app.Use(frontend.WithOptions(frontend.Options{
Prefix: "/app", // Serve from /app/*
}))
Routing:
/app → index.html
/app/about → index.html (SPA fallback)
/app/assets/main.js → assets/main.js
/ → Not handled by frontend middleware
Frontend router configuration required:
// React Router
<BrowserRouter basename="/app">
// Vite config
export default {
base: '/app/',
}
IgnorePaths
Paths that bypass the frontend middleware and go to Go handlers.
app.Use(frontend.WithOptions(frontend.Options{
IgnorePaths: []string{"/api", "/health", "/metrics"}, // Default
}))
Add custom paths:
app.Use(frontend.WithOptions(frontend.Options{
IgnorePaths: []string{"/api", "/auth", "/webhooks"},
}))
Disable all:
app.Use(frontend.WithOptions(frontend.Options{
IgnorePaths: []string{}, // No automatic ignores
}))
CacheControl
Configure caching behavior for different asset types.
app.Use(frontend.WithOptions(frontend.Options{
CacheControl: frontend.CacheConfig{
HashedAssets: 365 * 24 * time.Hour, // Default: 1 year
UnhashedAssets: 7 * 24 * time.Hour, // Default: 1 week
HTML: 0, // Default: no-cache
Patterns: map[string]time.Duration{
"*.woff2": 30 * 24 * time.Hour,
},
},
}))
CacheConfig fields:
- HashedAssets: Files with content hash in name (e.g.,
main.abc123.js)
- UnhashedAssets: Files without hash (e.g.,
logo.png)
- HTML: HTML files (
.html)
- Patterns: Custom patterns override defaults
See Caching Strategy for details.
Enable automatic security headers.
app.Use(frontend.WithOptions(frontend.Options{
SecurityHeaders: true, // Default
}))
Headers added:
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Disable if using helmet:
app.Use(helmet.Default())
app.Use(frontend.WithOptions(frontend.Options{
SecurityHeaders: false, // Avoid duplicates
}))
SourceMaps
Allow serving source map files (.js.map, .css.map).
app.Use(frontend.WithOptions(frontend.Options{
SourceMaps: false, // Default (blocked in production)
}))
Enable for debugging:
app.Use(frontend.WithOptions(frontend.Options{
SourceMaps: true,
}))
Conditional source maps:
isProduction := os.Getenv("MIZU_ENV") == "production"
app.Use(frontend.WithOptions(frontend.Options{
SourceMaps: !isProduction, // Only in development
}))
Manifest
Path to build manifest for asset mapping.
app.Use(frontend.WithOptions(frontend.Options{
Manifest: ".vite/manifest.json",
}))
The manifest is used for:
- Asset fingerprinting
- Module preloading
- CSS injection
- Template helpers
See Build Manifest for details.
InjectEnv
Inject server-side environment variables into the frontend.
app.Use(frontend.WithOptions(frontend.Options{
InjectEnv: []string{"API_URL", "ANALYTICS_ID"},
}))
Variables are exposed as window.__ENV__:
// Frontend code
const apiUrl = window.__ENV__.API_URL;
Security warning: Only inject non-sensitive values. These are visible to users.
See Environment Injection for details.
Inject custom meta tags into HTML.
app.Use(frontend.WithOptions(frontend.Options{
InjectMeta: map[string]string{
"description": "My awesome app",
"keywords": "go, mizu, web",
},
}))
Injects:
<meta name="description" content="My awesome app">
<meta name="keywords" content="go, mizu, web">
ServiceWorker
Path to service worker file for PWA support.
app.Use(frontend.WithOptions(frontend.Options{
ServiceWorker: "sw.js",
}))
Proper headers are added for service worker scope.
See Service Workers for details.
ErrorHandler
Custom error handler for frontend errors.
app.Use(frontend.WithOptions(frontend.Options{
ErrorHandler: func(c *mizu.Ctx, err error) error {
c.Logger().Error("frontend error", "error", err)
return c.JSON(500, map[string]string{
"error": "Internal Server Error",
})
},
}))
NotFoundHandler
Custom handler called before SPA fallback.
app.Use(frontend.WithOptions(frontend.Options{
NotFoundHandler: func(c *mizu.Ctx) error {
// Log 404s
c.Logger().Warn("file not found", "path", c.Request().URL.Path)
// Return nil to continue to SPA fallback
return nil
// Or return error to skip fallback and respond
// return c.JSON(404, map[string]string{"error": "not found"})
},
}))
Configuration Examples
Complete Production Setup
package server
import (
"embed"
"io/fs"
"os"
"time"
"github.com/go-mizu/mizu"
"github.com/go-mizu/mizu/frontend"
)
//go:embed all:../../dist
var distFS embed.FS
func New() *mizu.App {
app := mizu.New()
// API routes first
setupAPIRoutes(app)
// Frontend configuration
dist, _ := fs.Sub(distFS, "dist")
app.Use(frontend.WithOptions(frontend.Options{
// Mode
Mode: frontend.ModeProduction,
// Files
FS: dist,
Index: "index.html",
// Routing
Prefix: "",
IgnorePaths: []string{"/api"},
// Caching
CacheControl: frontend.CacheConfig{
HashedAssets: 365 * 24 * time.Hour,
UnhashedAssets: 7 * 24 * time.Hour,
HTML: 0,
Patterns: map[string]time.Duration{
"*.woff2": 30 * 24 * time.Hour,
},
},
// Security
SecurityHeaders: true,
SourceMaps: false,
// Environment
InjectEnv: []string{"API_URL"},
InjectMeta: map[string]string{
"description": "My app",
},
}))
return app
}
Development-Optimized Setup
func New() *mizu.App {
app := mizu.New()
setupAPIRoutes(app)
app.Use(frontend.WithOptions(frontend.Options{
Mode: frontend.ModeDev,
DevServer: "http://localhost:5173",
DevServerTimeout: 60 * time.Second,
ProxyWebSocket: true,
IgnorePaths: []string{"/api"},
}))
return app
}
Auto-Switching Setup
func New() *mizu.App {
app := mizu.New()
setupAPIRoutes(app)
dist, _ := fs.Sub(distFS, "dist")
app.Use(frontend.WithOptions(frontend.Options{
// Auto-detect based on MIZU_ENV
Mode: frontend.ModeAuto,
// Production settings
FS: dist,
Root: "./dist", // Fallback if FS fails
// Development settings
DevServer: "http://localhost:5173",
ProxyWebSocket: true,
// Common settings
IgnorePaths: []string{"/api"},
InjectEnv: []string{"API_URL"},
}))
return app
}
Multi-Environment Setup
type Config struct {
Environment string
Port string
DevPort string
}
func New(cfg *Config) *mizu.App {
app := mizu.New()
setupAPIRoutes(app)
opts := frontend.Options{
Mode: frontend.ModeAuto,
Root: "./dist",
DevServer: "http://localhost:" + cfg.DevPort,
IgnorePaths: []string{"/api"},
}
// Environment-specific configuration
switch cfg.Environment {
case "production":
dist, _ := fs.Sub(distFS, "dist")
opts.FS = dist
opts.SourceMaps = false
opts.SecurityHeaders = true
opts.InjectEnv = []string{"API_URL"}
case "staging":
opts.SourceMaps = true // Enable for debugging
opts.SecurityHeaders = true
opts.InjectEnv = []string{"API_URL", "DEBUG"}
case "development":
opts.Mode = frontend.ModeDev
opts.DevServerTimeout = 60 * time.Second
}
app.Use(frontend.WithOptions(opts))
return app
}
Environment Variables
Configure via environment:
# Mode detection
export MIZU_ENV=production # or development
export GO_ENV=production # alternative
export ENV=production # alternative
# Custom ports
export PORT=3000
export DEV_PORT=5173
# API URL injection
export API_URL=https://api.example.com
Access in configuration:
type Config struct {
Env string
Port string
DevPort string
APIURL string
}
func LoadConfig() *Config {
return &Config{
Env: getEnv("MIZU_ENV", "development"),
Port: getEnv("PORT", "3000"),
DevPort: getEnv("DEV_PORT", "5173"),
APIURL: getEnv("API_URL", "http://localhost:3000/api"),
}
}
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
Next Steps
Development Mode
Learn about dev mode and HMR
Production Mode
Optimize for production deployment
Caching Strategy
Deep dive into caching
Security
Security best practices