diff --git a/local-tool/server/internal/api/router.go b/local-tool/server/internal/api/router.go index 15fbf45..124b251 100644 --- a/local-tool/server/internal/api/router.go +++ b/local-tool/server/internal/api/router.go @@ -144,7 +144,17 @@ func broadcasterLogger(b *logger.Broadcaster) gin.HandlerFunc { } // spaFallback tries to serve the exact file from the embedded FS. -// If the file doesn't exist, it serves index.html for client-side routing. +// If the file doesn't exist, it finds the best matching route shell HTML +// for Next.js static export client-side routing. +// +// Next.js static export with generateStaticParams creates: +// /models/index.html — static page +// /models/_/index.html — dynamic route shell (placeholder param '_') +// +// For a request like /models/yolov5-face-detection: +// 1. Try exact file → not found +// 2. Try /models/_/index.html → found → serve it (Next.js CSR picks up real param from URL) +// 3. Fall back to /index.html (root) func spaFallback(staticFS http.FileSystem) gin.HandlerFunc { return func(c *gin.Context) { path := c.Request.URL.Path @@ -155,15 +165,31 @@ func spaFallback(staticFS http.FileSystem) gin.HandlerFunc { return } - // Try to serve the exact file (e.g., /models/index.html) - f, err := staticFS.Open(path) - if err == nil { + // Try to serve the exact file + if f, err := staticFS.Open(path); err == nil { f.Close() http.FileServer(staticFS).ServeHTTP(c.Writer, c.Request) return } - // Fall back to root index.html for SPA routing + // Try Next.js dynamic route shell: replace last path segment with '_' + // e.g. /models/yolov5 → /models/_/index.html + // /devices/kl520-0 → /devices/_/index.html + // /workspace/kl520-0 → /workspace/_/index.html + segments := strings.Split(strings.TrimRight(path, "/"), "/") + if len(segments) >= 2 { + segments[len(segments)-1] = "_" + shellPath := strings.Join(segments, "/") + "/index.html" + if f, err := staticFS.Open(shellPath); err == nil { + defer f.Close() + c.Header("Content-Type", "text/html; charset=utf-8") + c.Status(http.StatusOK) + _, _ = io.Copy(c.Writer, f) + return + } + } + + // Final fallback: root index.html index, err := staticFS.Open("/index.html") if err != nil { c.Status(http.StatusInternalServerError)