From 540fffdf3f6253cc07b95bbff5a62570f49cf9d5 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Tue, 24 Feb 2026 00:18:28 +0000 Subject: [PATCH] f1-stream: fix frontend routing with catch-all handler for SvelteKit SPA --- stacks/f1-stream/files/backend/main.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/stacks/f1-stream/files/backend/main.py b/stacks/f1-stream/files/backend/main.py index 744ccc4d..2a17d4d9 100644 --- a/stacks/f1-stream/files/backend/main.py +++ b/stacks/f1-stream/files/backend/main.py @@ -411,9 +411,31 @@ async def relay_endpoint( # --- Frontend Static Files --- # Mount the SvelteKit static build AFTER all API routes so API endpoints take priority. -_frontend_dir = os.path.join(os.path.dirname(__file__), "..", "frontend", "build") +# SvelteKit adapter-static with ssr=false produces {page}.html files and a fallback index.html. +# Starlette StaticFiles(html=True) only checks {path}/index.html, not {path}.html. +# We use a catch-all route to handle both patterns and the SPA fallback. +_frontend_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "frontend", "build")) if os.path.exists(_frontend_dir): - app.mount("/", StaticFiles(directory=_frontend_dir, html=True), name="frontend") + from starlette.responses import FileResponse, HTMLResponse + + _fallback_path = os.path.join(_frontend_dir, "index.html") + + @app.get("/{path:path}") + async def serve_frontend(path: str): + """Serve SvelteKit frontend files with SPA fallback.""" + for candidate in [ + os.path.join(_frontend_dir, path), + os.path.join(_frontend_dir, f"{path}.html"), + os.path.join(_frontend_dir, path, "index.html"), + ]: + real = os.path.realpath(candidate) + if real.startswith(_frontend_dir) and os.path.isfile(real): + return FileResponse(real) + # SPA fallback for client-side routing + if os.path.isfile(_fallback_path): + return FileResponse(_fallback_path) + return Response(content="Not Found", status_code=404) + logger.info("Serving frontend from %s", _frontend_dir) else: # Fallback root when no frontend build exists