package api import ( "io" "io/fs" "net/http" "strings" ) // StaticHandler serves embedded SPA files with fallback to index.html // for all non-API routes (SPA client-side routing support). func StaticHandler(webFS fs.FS) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Skip API routes — they are handled by the API router. if strings.HasPrefix(r.URL.Path, "/api") { http.NotFound(w, r) return } // Try to serve the exact file from the embedded FS. path := strings.TrimPrefix(r.URL.Path, "/") if path == "" { path = "index.html" } // Check if file exists in the embedded FS. if f, err := webFS.Open(path); err == nil { stat, statErr := f.Stat() f.Close() if statErr == nil && !stat.IsDir() { // Serve the actual file. Use http.ServeContent for correct MIME detection. file, _ := webFS.Open(path) defer file.Close() http.ServeContent(w, r, stat.Name(), stat.ModTime(), file.(io.ReadSeeker)) return } } // File not found: serve index.html for SPA client-side routing. indexFile, _ := webFS.Open("index.html") if indexFile != nil { defer indexFile.Close() stat, _ := indexFile.Stat() http.ServeContent(w, r, "index.html", stat.ModTime(), indexFile.(io.ReadSeeker)) } }) }