diff --git a/.gitignore b/.gitignore index 0ada0e3..070756b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ dist dist-ssr *.local +# Frontend build output (embedded at Go build time) +# Allow the fallback placeholder so embed always has at least index.html +!src/dist/index.html + # Environment files .env .env.local diff --git a/cmd/server/main.go b/cmd/server/main.go index 7ccbbc0..d2ee263 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,7 +3,9 @@ package main import ( "context" + "embed" "fmt" + "io/fs" "log" "net/http" "os" @@ -22,6 +24,9 @@ import ( "gopkg.in/yaml.v3" ) +//go:embed all:src/dist +var frontendFS embed.FS + // Config holds the application configuration. type Config struct { DBPath string `yaml:"db_path"` @@ -84,6 +89,9 @@ func main() { // API routes (auth required if API key is configured) r.Mount("/api/v1", auth.Middleware(cfg.APIKey)(apiRouter(sseHub, sqlDB))) + // Serve embedded React frontend with SPA fallback + r.Mount("/", frontendHandler()) + // Create server httpServer := &http.Server{ Addr: ":" + cfg.Port, @@ -158,4 +166,37 @@ func loadConfig(path string) (*Config, error) { } return &cfg, nil +} + +// frontendHandler returns an http.Handler that serves the embedded React +// frontend from src/dist/ with SPA-style fallback: any path that doesn't +// match a static file serves index.html for client-side routing. +// +// The frontend is embedded via //go:embed all:src/dist at build time. +// If src/dist/ is empty or missing at build time, the embedded fallback +// index.html (committed to the repo) is served instead, showing a +// "run npm run build" message. +func frontendHandler() http.Handler { + distFS, err := fs.Sub(frontendFS, "src/dist") + if err != nil { + // Shouldn't happen if embed worked, but be defensive. + panic("embedded frontend filesystem not found: " + err.Error()) + } + + fileServer := http.FileServer(http.FS(distFS)) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Try to serve the requested file. + f, err := distFS.Open(r.URL.Path[1:]) // strip leading "/" + if err != nil { + // File not found — serve index.html for SPA routing. + r.URL.Path = "/" + fileServer.ServeHTTP(w, r) + return + } + f.Close() + + // File exists, serve it. + fileServer.ServeHTTP(w, r) + }) } \ No newline at end of file diff --git a/src/dist/index.html b/src/dist/index.html new file mode 100644 index 0000000..097bd25 --- /dev/null +++ b/src/dist/index.html @@ -0,0 +1,42 @@ + + + + + + RemoteRig - Frontend Not Built + + + +
+

Frontend Not Built

+

The React frontend has not been built yet.

+

Run npm run build from the project root, then rebuild the Go binary.

+

API is still available at /api/v1/ and health at /health

+
+ + \ No newline at end of file