Skip to content

Commit a548cf7

Browse files
authored
Add live-reload support for frontend development (#360)
1 parent a192f66 commit a548cf7

File tree

4 files changed

+71
-3
lines changed

4 files changed

+71
-3
lines changed

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ build-go: # Build Go binary (doesn't rebuild frontend)
1818
cp pkg/web/dev.html $(FRONTEND_BUILD_DIR)/index.html
1919
go build -o bin/quickpizza ./cmd
2020

21+
.PHONY: install-web
22+
install-web: # Install frontend dependencies
23+
cd pkg/web && npm install
24+
25+
.PHONY: dev
26+
dev: # Run with live-reload (frontend dev server + backend with -dev flag)
27+
@echo "Starting dev server with live-reload"
28+
@trap 'kill 0' EXIT; \
29+
(export PUBLIC_BACKEND_ENDPOINT="http://localhost:3333" && \
30+
export PUBLIC_BACKEND_WS_ENDPOINT="ws://localhost:3333/ws" && \
31+
cd pkg/web && npm run dev) & \
32+
go run ./cmd -dev
33+
2134
.PHONY: proto
2235
proto: # Generate protobuf files
2336
protoc --go_out=. --go-grpc_out=. proto/quickpizza.proto

cmd/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"flag"
56
http "net/http"
67
"os"
78
"runtime"
@@ -21,7 +22,10 @@ import (
2122
"go.opentelemetry.io/otel/propagation"
2223
)
2324

25+
var devMode = flag.Bool("dev", false, "Run in development mode with Vite dev server")
26+
2427
func main() {
28+
flag.Parse()
2529
// write logs as JSON
2630
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
2731
Level: logging.GetLogLevel(),
@@ -96,7 +100,7 @@ func main() {
96100

97101
if envServe("QUICKPIZZA_ENABLE_PUBLIC_API_SERVICE") {
98102
// Serve frontend static assets
99-
server.AddFrontend()
103+
server.AddFrontend(*devMode)
100104

101105
// If running as a microservice (not all services in one instance),
102106
// also act as a gateway to proxy public-facing endpoints

docs/development.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@ Running QuickPizza locally.
44

55
**Prerequisites**: Go 1.21+ and Node.js 18+
66

7+
## Development Mode (with Live-Reload)
8+
9+
For the best development experience with automatic frontend reloading:
10+
11+
1. Install frontend dependencies (first time only):
12+
```bash
13+
make install-web
14+
```
15+
16+
2. Start dev server with live-reload:
17+
```bash
18+
make dev
19+
```
20+
21+
3. Access at `http://localhost:3333`
22+
23+
This starts both:
24+
- Vite dev server on port 5173 (with hot module replacement)
25+
- Go backend on port 3333 (proxies to Vite for frontend assets)
26+
27+
Frontend changes will reload instantly without rebuilding or restarting the server.
28+
29+
## Production Build
30+
31+
To build and run as in production:
32+
733
1. Build frontend:
834
```bash
935
make build-web
@@ -17,3 +43,4 @@ Running QuickPizza locally.
1743
3. Access at `http://localhost:3333`
1844

1945

46+

pkg/http/http.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ func (s *Server) AddPrometheusHandler() {
346346
}
347347

348348
// AddFrontend enables serving the embedded Svelte frontend.
349-
func (s *Server) AddFrontend() {
349+
func (s *Server) AddFrontend(devMode bool) {
350350
s.router.Group(func(r chi.Router) {
351351
s.traceInstaller.Install(r, "frontend",
352352
// The frontend serves a lot of static files on different paths. To save on cardinality, we override the
@@ -357,7 +357,14 @@ func (s *Server) AddFrontend() {
357357
)
358358

359359
r.Handle("/favicon.ico", FaviconHandler())
360-
r.Handle("/*", SvelteKitHandler())
360+
361+
if devMode {
362+
// In dev mode, proxy to Vite dev server
363+
r.Handle("/*", ViteProxyHandler())
364+
} else {
365+
// Production: serve embedded files
366+
r.Handle("/*", SvelteKitHandler())
367+
}
361368
})
362369
}
363370

@@ -1533,6 +1540,23 @@ func SvelteKitHandler() http.Handler {
15331540
})
15341541
}
15351542

1543+
// ViteProxyHandler returns an http.Handler that proxies requests to the Vite dev server.
1544+
func ViteProxyHandler() http.Handler {
1545+
target, _ := url.Parse("http://localhost:5173")
1546+
proxy := httputil.NewSingleHostReverseProxy(target)
1547+
proxy.Director = func(req *http.Request) {
1548+
req.URL.Scheme = target.Scheme
1549+
req.URL.Host = target.Host
1550+
req.Host = target.Host
1551+
slog.Debug("Proxying request to Vite", "path", req.URL.Path)
1552+
}
1553+
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
1554+
slog.Error("Vite proxy error", "err", err, "path", r.URL.Path)
1555+
http.Error(w, "Vite dev server unavailable. Make sure it's running on http://localhost:5173", http.StatusBadGateway)
1556+
}
1557+
return proxy
1558+
}
1559+
15361560
func PrometheusMiddleware(next http.Handler) http.Handler {
15371561
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15381562
start := time.Now()

0 commit comments

Comments
 (0)