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.
Production mode is optimized for performance, security, and reliability. When you deploy your Mizu app, it serves pre-built static files with intelligent caching, compression, and security headers.
How Production Mode Works
In production, Mizu serves static files directly from the filesystem or embedded FS:
Browser Request
↓
Mizu Server
↓
Is it /api/* ?
↙ ↘
Yes No
↓ ↓
Go API Static File Server
Handler ↓
Is file found?
↙ ↘
Yes No
↓ ↓
Serve File Serve index.html
(with cache) (SPA fallback)
The flow:
Browser requests a path
Mizu checks if it’s an API route
If no, check if a static file exists at that path
If file exists → serve it with appropriate cache headers
If not → serve index.html for SPA routing
Enabling Production Mode
Automatic Detection
Use auto-detection based on environment:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeAuto , // Auto-detect
Root : "./dist" , // Build directory
DevServer : "http://localhost:5173" ,
}))
Set environment to production:
MIZU_ENV = production ./server
Explicit Production Mode
Force production mode:
app . Use ( frontend . New ( "./dist" ))
Or with options:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
}))
Building Your Frontend
Before deploying, build your frontend:
Vite (React/Vue/Svelte)
cd frontend
npm run build
Creates optimized files in dist/:
Minified JavaScript
Minified CSS
Optimized images
Asset fingerprinting (e.g., main.abc123.js)
Angular
cd frontend
ng build --configuration=production
Creates files in dist/my-app/browser/.
Next.js / Nuxt
cd frontend
npm run build
npm run export # Creates static export
Build Output Structure
A typical Vite build creates:
dist/
├── index.html # Entry point
├── assets/
│ ├── main.abc123.js # Hashed JS (fingerprinted)
│ ├── main.def456.css # Hashed CSS (fingerprinted)
│ ├── logo.svg # Unhashed asset
│ └── logo.abc789.svg # Hashed copy (if imported)
└── .vite/
└── manifest.json # Build manifest
Key points:
Hashed files (main.abc123.js) can be cached forever
index.html should never be cached (cache: no-cache)
Manifest maps source files to output files
SPA Fallback Routing
For Single Page Applications, all routes should serve index.html to let the frontend router handle navigation.
app . Use ( frontend . New ( "./dist" ))
Automatic fallback:
/ → dist/index.html
/about → dist/index.html (file doesn’t exist)
/users/123 → dist/index.html (file doesn’t exist)
/assets/main.js → dist/assets/main.js (file exists)
/api/users → Go handler (ignored path)
The frontend router (React Router, Vue Router, etc.) then handles the routing.
Caching Strategy
Mizu applies intelligent caching based on file type and fingerprinting.
Default Cache Durations
Asset Type Cache-Control Duration Hashed files (main.abc123.js) public, max-age=31536000, immutable1 year Unhashed files (logo.png) public, max-age=6048001 week HTML files no-cache, no-store, must-revalidateNone Source maps (.map) no-cacheNone
How Hashing Works
Hashed filename (content hash in name):
main.abc123.js ← Hash of file contents
If the file content changes, the hash changes:
main.xyz789.js ← New hash = new filename
Benefits:
Old version stays cached (doesn’t break users on old version)
New version has new filename (forces fresh download)
Can cache aggressively with immutable directive
Custom Cache Configuration
Override default cache durations:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
CacheControl : frontend . CacheConfig {
HashedAssets : 365 * 24 * time . Hour , // 1 year (default)
UnhashedAssets : 7 * 24 * time . Hour , // 1 week (default)
HTML : 0 , // No cache (default)
},
}))
Custom Pattern-Based Caching
Cache specific file types differently:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
CacheControl : frontend . CacheConfig {
HashedAssets : 365 * 24 * time . Hour ,
Patterns : map [ string ] time . Duration {
"*.woff2" : 30 * 24 * time . Hour , // Fonts: 30 days
"*.png" : 14 * 24 * time . Hour , // Images: 14 days
"*.svg" : 14 * 24 * time . Hour , // SVGs: 14 days
},
},
}))
Cache logic:
Check custom patterns first
If no match, check if filename has hash
If hashed → use HashedAssets duration
If unhashed → use UnhashedAssets duration
If HTML → use HTML duration (or no-cache)
Mizu automatically adds security headers in production:
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
These protect against:
MIME sniffing attacks (nosniff)
Clickjacking (SAMEORIGIN)
XSS attacks (legacy protection)
Referrer leaking (privacy)
If you’re using the helmet middleware :
app . Use ( helmet . Default ()) // Apply helmet headers
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
SecurityHeaders : false , // Disable built-in headers
}))
This avoids duplicate headers.
Source Maps
Source maps help debug production errors by mapping minified code back to source code.
Default Behavior
Source maps (.js.map, .css.map) are blocked in production for security:
GET /assets/main.js.map → 404 Not Found
Why block them?
Exposes source code structure
Larger file sizes
Not needed by end users
Enable Source Maps
For internal or staging environments:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
SourceMaps : true , // Allow .map files
}))
Conditional Source Maps
Enable only for specific environments:
isStaging := os . Getenv ( "ENV" ) == "staging"
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
SourceMaps : isStaging , // Only in staging
}))
Embedded Filesystems
For single-binary deployment, embed your frontend into the Go binary:
package server
import (
" embed "
" io/fs "
" 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 ()
// Extract dist subdirectory
dist , _ := fs . Sub ( distFS , "dist" )
app . Use ( frontend . WithFS ( dist ))
return app
}
Benefits:
Single binary contains both backend and frontend
No need to deploy dist/ folder separately
Simpler deployment
Faster startup (no disk reads)
Trade-offs:
Larger binary size
Can’t update frontend without rebuilding
Need to rebuild for frontend changes
See Embedded Filesystems for details.
URL Prefix
Serve frontend from a subdirectory:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
Prefix : "/app" , // Serve from /app/*
}))
Routing:
/ → Go handler (not fronted)
/app → dist/index.html
/app/about → dist/index.html (SPA fallback)
/app/assets/main.js → dist/assets/main.js
Frontend router config:
// React Router
< BrowserRouter basename = "/app" >
< Routes >
< Route path = "/" element = { < Home /> } />
< Route path = "/about" element = { < About /> } />
</ Routes >
</ BrowserRouter >
// Vite config
export default {
base: '/app/' , // Set base URL
}
Custom Index File
Use a different entry point:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
Index : "app.html" , // Default: "index.html"
}))
Error Handling
Custom Not Found Handler
Run custom logic before SPA fallback:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
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
// return c.JSON(404, map[string]string{"error": "not found"})
},
}))
Custom Error Handler
Handle all errors:
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
Root : "./dist" ,
ErrorHandler : func ( c * mizu . Ctx , err error ) error {
c . Logger (). Error ( "frontend error" , "error" , err )
return c . Text ( 500 , "Internal Server Error" )
},
}))
Compression
For better performance, use the compress middleware :
import " github.com/go-mizu/mizu/middlewares/compress "
// Apply compression before frontend
app . Use ( compress . Default ())
app . Use ( frontend . New ( "./dist" ))
This compresses responses with gzip or brotli, reducing transfer size by 60-80% for text files.
Complete Production Example
Here’s a production-ready setup:
package server
import (
" embed "
" io/fs "
" time "
" github.com/go-mizu/mizu "
" github.com/go-mizu/mizu/middlewares/compress "
" github.com/go-mizu/mizu/frontend "
" github.com/go-mizu/mizu/middlewares/helmet "
" github.com/go-mizu/mizu/middlewares/recover "
)
//go:embed all:../../dist
var distFS embed . FS
func New () * mizu . App {
app := mizu . New ()
// Essential middleware
app . Use ( recover . New ())
app . Use ( helmet . Default ())
app . Use ( compress . Default ())
// API routes
app . Get ( "/api/users" , handleUsers )
// Frontend (embedded, optimized)
dist , _ := fs . Sub ( distFS , "dist" )
app . Use ( frontend . WithOptions ( frontend . Options {
Mode : frontend . ModeProduction ,
FS : dist ,
IgnorePaths : [] string { "/api" },
SecurityHeaders : false , // helmet already set headers
SourceMaps : false , // Block in production
CacheControl : frontend . CacheConfig {
HashedAssets : 365 * 24 * time . Hour ,
UnhashedAssets : 7 * 24 * time . Hour ,
Patterns : map [ string ] time . Duration {
"*.woff2" : 30 * 24 * time . Hour ,
},
},
}))
return app
}
1. Asset Fingerprinting
Ensure your build tool fingerprints assets:
// vite.config.ts
export default {
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js' ,
chunkFileNames: 'assets/[name].[hash].js' ,
assetFileNames: 'assets/[name].[hash].[ext]'
}
}
}
}
2. Code Splitting
Split code into smaller chunks:
// React lazy loading
const About = lazy (() => import ( './pages/About' ));
< Suspense fallback = { < div > Loading... </ div > } >
< Route path = "/about" element = { < About /> } />
</ Suspense >
3. Tree Shaking
Import only what you need:
// ❌ Imports entire library
import _ from 'lodash' ;
// ✅ Imports only debounce
import debounce from 'lodash/debounce' ;
4. Image Optimization
Optimize images before bundling:
Use WebP format for images
Compress with tools like imagemin
Use responsive images (srcset)
Lazy load images below the fold
5. CSS Optimization
Remove unused CSS (PurgeCSS, built into Tailwind)
Minify CSS in build
Inline critical CSS for faster first paint
Monitoring Production
Access Logs
Use the logger middleware :
import " github.com/go-mizu/mizu/middlewares/logger "
app . Use ( logger . New ())
Logs show all requests:
GET / 200 (12ms)
GET /assets/main.js 200 (2ms)
GET /api/users 200 (45ms)
GET /about 200 (3ms)
Metrics
Use Prometheus or custom metrics :
import " github.com/go-mizu/mizu/middlewares/prometheus "
app . Use ( prometheus . New ())
Track:
Request count
Response times
Error rates
Cache hit rates
Next Steps
Configuration Explore all configuration options
Caching Deep dive into caching strategies
Security Security best practices
Deployment Build and deployment guide