Go Embedded SPA
Overview
Go Embedded SPA is a technique that embeds frontend SPA (Single Page Application) static resources (React/Vue/TSX) into Go binary files using Go 1.16+ embed package, achieving single-binary full-stack deployment.
Core Benefits
| Benefit | Description |
|---|---|
| 🎯 Single File Deploy | One binary contains both frontend and backend, no nginx needed |
| 🌍 Cross-Platform | GOOS/GOARCH easily compiles for Linux/Mac/Windows |
| 📦 Zero Dependencies | Target machine needs no Node.js/npm, uses Go standard library only |
| 🚀 Container Friendly | Dockerfile only needs COPY + ENTRYPOINT |
| 🔒 Resource Security | Static resources compiled into binary, tamper-proof |
| ⚡ Fast Startup | No disk I/O for loading static files |
Project Structure
code
project/
├── go.mod
├── Makefile
├── site/ # Frontend project
│ ├── embed.go # Go embed directive
│ ├── src/ # React/Vue source
│ ├── dist/ # Build output (embedded)
│ ├── package.json
│ ├── vite.config.ts
│ └── index.html
├── pkg/
│ └── siteserver/ # Static file server
│ └── siteserver.go
└── cmd/
└── app/
└── main.go
Implementation Steps
Step 1: Create embed.go
Create site/embed.go to declare embed directive. See references/embed.md for complete code.
Key points:
- •Use
//go:embed all:distto embed all files including hidden files - •Use
fs.Sub()to removedist/prefix
Step 2: Create Static File Server
Create pkg/siteserver/siteserver.go. See references/siteserver.md for complete implementation using Go standard net/http.
Core logic:
- •Pre-load
index.htmlfor SPA fallback - •Create
http.FileServerfrom embed.FS - •Detect static resources by file extension
- •Return
index.htmlfor SPA routes (no file extension)
Step 3: Application Integration
go
package main
import (
"log"
"net/http"
"your-project/pkg/siteserver"
"your-project/site"
)
func main() {
mux := http.NewServeMux()
// 1. Register API routes FIRST
mux.HandleFunc("/apis/v1/health", healthHandler)
mux.HandleFunc("/apis/v1/data", dataHandler)
// 2. Wrap with static file server (as fallback)
handler := siteserver.WrapHandler(mux, site.DistDirFS)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Order is critical: API routes must be registered before static file server.
Step 4: Development
Recommended: Start both backend and frontend with one command:
bash
make dev # Backend: http://localhost:8080 (Go) # Frontend: http://localhost:5173 (Vite with hot reload) # API Proxy: /api/* -> localhost:8080
Press Ctrl+C to stop both servers.
Step 5: Build for Production
Build order: frontend first, then backend
bash
make build # Build both (frontend + backend) # Or separately: make build-web # npm run build → site/dist/ make build-backend # go build (embeds dist/)
Step 6: Cross-Platform Build
bash
make build-linux # Linux amd64 make build-linux-arm64 # Linux arm64 make build-macos # macOS Intel make build-macos-arm64 # macOS Apple Silicon make build-windows # Windows amd64 make build-all # All platforms
Request Handling Flow
code
Browser Request
│
▼
┌─────────────────────────────────────┐
│ http.ServeMux Route Matching │
│ ├── /apis/* → API Handler │
│ └── Others → Static Handler │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Static Handler │
│ ├── Has extension → serve file │
│ └── No extension → return index.html│
└─────────────────────────────────────┘
Caching Strategy
| Path | Cache-Control | Reason |
|---|---|---|
/assets/* | max-age=31536000, immutable | Files have hash in name |
/index.html | no-cache | Entry must be fresh |
Container Deployment
Minimal Dockerfile:
dockerfile
FROM scratch COPY app /app ENTRYPOINT ["/app"]
Troubleshooting
- •Empty dist error → Run
make build-webbeforemake build-backend - •Static files 404 → Check
//go:embed all:distpath relative to embed.go - •API not matching → Register API routes BEFORE wrapping with siteserver
- •SPA routes 404 → Verify handler returns index.html for non-file paths
References
- •
go-dependencies.md- Go module dependencies (standard library only) - •
embed.md- Complete embed.go implementation - •
siteserver.md- Static file server using Go standard net/http - •
vite-config.md- Vite configuration for development proxy - •
makefile.md- Complete Makefile with cross-platform build