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"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed all:dist
|
||||
|
|
@ -24,6 +26,44 @@ func registerEmbedRoutes(mux *http.ServeMux) {
|
|||
return
|
||||
}
|
||||
|
||||
// Serve the static files at the root route
|
||||
mux.Handle("/", http.FileServer(http.FS(subFS)))
|
||||
fileServer := 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