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
This commit is contained in:
2026-03-28 13:34:02 +03:00
parent 652229c67f
commit 4d4e07eb2e
3 changed files with 21 additions and 14 deletions
+3 -1
View File
@@ -88,9 +88,11 @@ func (s *Server) Router() chi.Router {
r.Use(recovery) r.Use(recovery)
r.Use(logging) r.Use(logging)
r.Use(cors) r.Use(cors)
r.Use(jsonContentType)
r.Route("/api", func(r chi.Router) { 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). // Public auth endpoints (no auth required).
r.Post("/auth/login", s.login) r.Post("/auth/login", s.login)
r.Get("/auth/oidc/login", s.oidcLogin) r.Get("/auth/oidc/login", s.oidcLogin)
+17 -12
View File
@@ -1,6 +1,7 @@
package api package api
import ( import (
"io"
"io/fs" "io/fs"
"net/http" "net/http"
"strings" "strings"
@@ -9,8 +10,6 @@ import (
// StaticHandler serves embedded SPA files with fallback to index.html // StaticHandler serves embedded SPA files with fallback to index.html
// for all non-API routes (SPA client-side routing support). // for all non-API routes (SPA client-side routing support).
func StaticHandler(webFS fs.FS) http.Handler { func StaticHandler(webFS fs.FS) http.Handler {
fileServer := http.FileServer(http.FS(webFS))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Skip API routes — they are handled by the API router. // Skip API routes — they are handled by the API router.
if strings.HasPrefix(r.URL.Path, "/api") { if strings.HasPrefix(r.URL.Path, "/api") {
@@ -18,25 +17,31 @@ func StaticHandler(webFS fs.FS) http.Handler {
return return
} }
// Try to serve the exact file. // Try to serve the exact file from the embedded FS.
path := strings.TrimPrefix(r.URL.Path, "/") path := strings.TrimPrefix(r.URL.Path, "/")
if path == "" { if path == "" {
path = "index.html" path = "index.html"
} }
// Check if file exists in the embedded FS. // Check if file exists in the embedded FS.
f, err := webFS.Open(path) if f, err := webFS.Open(path); err == nil {
if err == nil { stat, statErr := f.Stat()
f.Close() f.Close()
// Clear the JSON content-type set by middleware — let file server decide. if statErr == nil && !stat.IsDir() {
w.Header().Del("Content-Type") // Serve the actual file. Use http.ServeContent for correct MIME detection.
fileServer.ServeHTTP(w, r) file, _ := webFS.Open(path)
return 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. // File not found: serve index.html for SPA client-side routing.
r.URL.Path = "/" indexFile, _ := webFS.Open("index.html")
w.Header().Del("Content-Type") if indexFile != nil {
fileServer.ServeHTTP(w, r) defer indexFile.Close()
stat, _ := indexFile.Stat()
http.ServeContent(w, r, "index.html", stat.ModTime(), indexFile.(io.ReadSeeker))
}
}) })
} }
+1 -1
View File
@@ -5,5 +5,5 @@ import "embed"
// WebBuildFS holds the embedded SvelteKit static build output. // WebBuildFS holds the embedded SvelteKit static build output.
// The build directory is populated by running `npm run build` in the web/ directory. // 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 var WebBuildFS embed.FS