package spa import ( "embed" "io/fs" "net/http" "path" ) //go:embed all:dist var distFS embed.FS // DistFS returns the embedded dist directory as an fs.FS. func DistFS() (fs.FS, error) { return fs.Sub(distFS, "dist") } // NewHandler returns an HTTP handler that: // - Serves static files from the embedded dist/ directory // - Falls back to index.html for SPA routing (any non-API path) func NewHandler() http.Handler { dist, err := DistFS() if err != nil { panic("spa: embedded dist not found: " + err.Error()) } return &spaHandler{dist: dist} } type spaHandler struct { dist fs.FS } func (h *spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // API paths are handled by Go API handlers - this should never be reached // since Go mux dispatches to specific handlers first. But if reached, // pass through to FileServer which will return 404 for unknown paths. // Try to serve the requested file first filePath := path.Clean(r.URL.Path) f, err := h.dist.Open(filePath) if err == nil { f.Close() // File exists - serve it via FileServer http.FileServer(http.FS(h.dist)).ServeHTTP(w, r) return } // Fallback to index.html for SPA routing indexFile, err := h.dist.Open("index.html") if err != nil { http.Error(w, "index.html not found in embedded files", http.StatusInternalServerError) return } indexFile.Close() http.FileServer(http.FS(h.dist)).ServeHTTP(w, r) }