internal/server/web/spa.go
Ref: Size: 1.2 KiB History
// Package web embeds the built SvelteKit single-page app and serves it with
// SPA-style fallback (unknown paths resolve to index.html for client routing).
package web
import (
"io/fs"
"net/http"
"path"
"strings"
)
// spaHandler serves static files from fsys, falling back to index.html for any
// path that doesn't resolve to a real file (so client-side routes like
// /vms/abc load the app). If index.html is absent (UI not built yet) it returns
// a clear 503 instead of a confusing 404.
func spaHandler(fsys fs.FS) http.Handler {
fileServer := http.FileServerFS(fsys)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(path.Clean("/"+r.URL.Path), "/")
if p != "" && fileExists(fsys, p) {
fileServer.ServeHTTP(w, r)
return
}
if !fileExists(fsys, "index.html") {
http.Error(w, "UI not built — run `make web`", http.StatusServiceUnavailable)
return
}
// SPA fallback: serve index.html for unknown (client-routed) paths.
r2 := r.Clone(r.Context())
r2.URL.Path = "/"
fileServer.ServeHTTP(w, r2)
})
}
func fileExists(fsys fs.FS, name string) bool {
f, err := fsys.Open(name)
if err != nil {
return false
}
defer f.Close()
st, err := f.Stat()
return err == nil && !st.IsDir()
}