fix(web): add SPA index fallback for embedded frontend routes
Serve existing static assets as-is, keep /api/* and missing asset paths returning 404, and add tests for SPA fallback behavior on refresh.
This commit is contained in:
parent
1159646345
commit
71cd03430a
2 changed files with 92 additions and 2 deletions
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:dist
|
//go:embed all:dist
|
||||||
|
|
@ -24,6 +26,44 @@ func registerEmbedRoutes(mux *http.ServeMux) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve the static files at the root route
|
fileServer := http.FileServer(http.FS(subFS))
|
||||||
mux.Handle("/", http.FileServer(http.FS(subFS)))
|
|
||||||
|
// Serve static assets and fallback to index.html for SPA routes.
|
||||||
|
mux.Handle(
|
||||||
|
"/",
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep unknown API paths as 404 instead of falling back to SPA entry.
|
||||||
|
if r.URL.Path == "/api" || strings.HasPrefix(r.URL.Path, "/api/") {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPath := path.Clean(strings.TrimPrefix(r.URL.Path, "/"))
|
||||||
|
if cleanPath == "." {
|
||||||
|
cleanPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing static files/directories should be served directly.
|
||||||
|
if cleanPath != "" {
|
||||||
|
if _, statErr := fs.Stat(subFS, cleanPath); statErr == nil {
|
||||||
|
fileServer.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Missing asset-like paths should remain 404.
|
||||||
|
if strings.Contains(path.Base(cleanPath), ".") {
|
||||||
|
fileServer.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexReq := r.Clone(r.Context())
|
||||||
|
indexReq.URL.Path = "/"
|
||||||
|
fileServer.ServeHTTP(w, indexReq)
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
50
web/backend/embed_test.go
Normal file
50
web/backend/embed_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSPARouteFallsBackToIndex(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
registerEmbedRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/providers", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rr.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
if !strings.Contains(rr.Body.String(), `<div id="root"></div>`) {
|
||||||
|
t.Fatalf("response does not look like index.html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownAPIPathStays404(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
registerEmbedRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/not-found", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusNotFound {
|
||||||
|
t.Fatalf("status = %d, want %d", rr.Code, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingAssetStays404(t *testing.T) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
registerEmbedRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/assets/not-found.js", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusNotFound {
|
||||||
|
t.Fatalf("status = %d, want %d", rr.Code, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue