From 4d4e07eb2e9eef2a0311636a335233f9c06ddfac Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 28 Mar 2026 13:34:02 +0300 Subject: [PATCH] fix: serve SvelteKit _app assets correctly - Use all: prefix in go:embed to include _app/ directory (Go skips _-prefixed dirs by default) - Move jsonContentType middleware to /api route group only - Use http.ServeContent for proper MIME type detection - Remove unused fileServer variable --- internal/api/router.go | 4 +++- internal/api/static.go | 29 +++++++++++++++++------------ web.go | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/internal/api/router.go b/internal/api/router.go index 7c3c158..d729088 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -88,9 +88,11 @@ func (s *Server) Router() chi.Router { r.Use(recovery) r.Use(logging) r.Use(cors) - r.Use(jsonContentType) r.Route("/api", func(r chi.Router) { + // JSON content type only for API routes (not static files). + r.Use(jsonContentType) + // Public auth endpoints (no auth required). r.Post("/auth/login", s.login) r.Get("/auth/oidc/login", s.oidcLogin) diff --git a/internal/api/static.go b/internal/api/static.go index ed35efc..ff2bdfe 100644 --- a/internal/api/static.go +++ b/internal/api/static.go @@ -1,6 +1,7 @@ package api import ( + "io" "io/fs" "net/http" "strings" @@ -9,8 +10,6 @@ import ( // 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 { - fileServer := http.FileServer(http.FS(webFS)) - 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") { @@ -18,25 +17,31 @@ func StaticHandler(webFS fs.FS) http.Handler { return } - // Try to serve the exact file. + // 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. - f, err := webFS.Open(path) - if err == nil { + if f, err := webFS.Open(path); err == nil { + stat, statErr := f.Stat() f.Close() - // Clear the JSON content-type set by middleware — let file server decide. - w.Header().Del("Content-Type") - fileServer.ServeHTTP(w, r) - return + 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. - r.URL.Path = "/" - w.Header().Del("Content-Type") - fileServer.ServeHTTP(w, r) + 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)) + } }) } diff --git a/web.go b/web.go index c9f1cd2..5d23f87 100644 --- a/web.go +++ b/web.go @@ -5,5 +5,5 @@ import "embed" // WebBuildFS holds the embedded SvelteKit static build output. // The build directory is populated by running `npm run build` in the web/ directory. // -//go:embed web/build +//go:embed all:web/build var WebBuildFS embed.FS