serve the embedded frontend alongside the API, slice 1 of M4

//go:embed needs internal/webdist/dist to exist at compile time, so a
one-line placeholder index.html is committed there and the real build
(Docker, or a manual copy of web/dist) overwrites it. One mux serves
/, /api, and a /healthz for the compose healthcheck to come.
This commit is contained in:
Justin Visser 2026-06-11 14:02:38 +02:00
parent bbba450c6e
commit 3684488d75
4 changed files with 58 additions and 5 deletions

4
internal/webdist/dist/index.html vendored Normal file
View file

@ -0,0 +1,4 @@
<!doctype html>
<meta charset="utf-8" />
<title>spreadlab</title>
<p>Placeholder page: this binary was built without the frontend. Run <code>npm run build</code> in <code>web/</code> and copy <code>web/dist</code> over <code>internal/webdist/dist</code>, or use the Docker image.</p>

View file

@ -0,0 +1,33 @@
// Package webdist embeds the built frontend so one binary serves both
// the dashboard and the API. `//go:embed` needs its directory to exist
// at compile time, but the real build output (web/dist) is gitignored,
// so this package commits a one-line placeholder dist/index.html that
// keeps `go build` and `go test ./...` working everywhere. The
// Dockerfile overwrites the placeholder with the real `npm run build`
// output before compiling; to try that locally, copy web/dist over
// internal/webdist/dist and `git restore` it afterwards.
package webdist
import (
"embed"
"io/fs"
"net/http"
)
// The all: prefix embeds files whose names start with . or _ too, which
// plain go:embed skips; vite output can contain such assets.
//
//go:embed all:dist
var embeddedFiles embed.FS
// Handler serves the embedded frontend. The dashboard is a single page
// without client-side routes, so unknown paths get the file server's
// plain 404 rather than a fallback to index.html.
func Handler() http.Handler {
distRoot, err := fs.Sub(embeddedFiles, "dist")
if err != nil {
// Unreachable: "dist" is compiled into embeddedFiles above.
panic(err)
}
return http.FileServer(http.FS(distRoot))
}