From b3723b2167d4e2e4b9fd2665a7e943b3253234c6 Mon Sep 17 00:00:00 2001 From: benya Date: Fri, 3 Apr 2026 01:22:30 +0300 Subject: [PATCH] fix: serve built frontend from backend --- Makefile | 5 +++- internal/httpapi/router.go | 47 ++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index adfaf73..3d4adf6 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ frontend-dev: frontend-build: npm --prefix apps/web run build +full-build: + npm --prefix apps/web run build + go test ./... + dev: @echo "Run backend and frontend in separate terminals." - diff --git a/internal/httpapi/router.go b/internal/httpapi/router.go index 7793bc7..83cbf42 100644 --- a/internal/httpapi/router.go +++ b/internal/httpapi/router.go @@ -4,8 +4,10 @@ import ( "database/sql" "encoding/json" "errors" + "log" "net/http" "os" + "path/filepath" "strconv" "strings" "time" @@ -104,8 +106,11 @@ func NewRouter(cfg config.Config, database *sql.DB, scanService *scanner.Service }) }) - fs := http.FileServer(http.Dir("./web")) - r.Handle("/*", spaFallback(fs)) + if frontendRoot := detectFrontendRoot(); frontendRoot != "" { + r.NotFound(spaHandler(frontendRoot)) + } else { + log.Printf("frontend assets not found; API routes are available, but web UI is not being served") + } return r } @@ -644,12 +649,46 @@ func optionalBool(raw string) *bool { } } -func spaFallback(next http.Handler) http.HandlerFunc { +func spaHandler(root string) http.HandlerFunc { + fileServer := http.FileServer(http.Dir(root)) + indexPath := filepath.Join(root, "index.html") + return func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api") || strings.HasPrefix(r.URL.Path, "/rest") || strings.HasPrefix(r.URL.Path, "/health") { http.NotFound(w, r) return } - next.ServeHTTP(w, r) + + cleanPath := filepath.Clean(strings.TrimPrefix(r.URL.Path, "/")) + if cleanPath == "." { + http.ServeFile(w, r, indexPath) + return + } + + fullPath := filepath.Join(root, cleanPath) + if info, err := os.Stat(fullPath); err == nil && !info.IsDir() { + fileServer.ServeHTTP(w, r) + return + } + + http.ServeFile(w, r, indexPath) } } + +func detectFrontendRoot() string { + candidates := []string{ + "./apps/web/dist", + "./web", + filepath.Join(".", "apps", "web", "dist"), + filepath.Join(".", "web"), + } + + for _, candidate := range candidates { + info, err := os.Stat(candidate) + if err == nil && info.IsDir() { + return candidate + } + } + + return "" +}