From 8e61dc4df4efb74e8219e78868a90d653640ed5b Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 14 May 2026 07:36:16 -0400 Subject: [PATCH] feat: serve the built Vue SPA from Quart with history-mode fallback The frontend blueprint is registered after the api blueprint so /api/* routes are matched first; everything else falls through to the SPA. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/app/__init__.py | 3 +++ backend/app/frontend.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 backend/app/frontend.py diff --git a/backend/app/__init__.py b/backend/app/__init__.py index d1081c2..cd295e9 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -6,6 +6,7 @@ from quart import Quart from .api import api_bp from .config import get_config +from .frontend import frontend_bp def create_app() -> Quart: @@ -15,5 +16,7 @@ def create_app() -> Quart: app = Quart(__name__) app.secret_key = cfg.secret_key app.register_blueprint(api_bp) + # Registered last so /api/* routes win over the SPA catch-all. + app.register_blueprint(frontend_bp) return app diff --git a/backend/app/frontend.py b/backend/app/frontend.py new file mode 100644 index 0000000..879a6c9 --- /dev/null +++ b/backend/app/frontend.py @@ -0,0 +1,17 @@ +"""Serves the built Vue SPA from frontend/dist/ with history-mode fallback.""" + +from pathlib import Path + +from quart import Blueprint, send_from_directory + +FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist" + +frontend_bp = Blueprint("frontend", __name__) + + +@frontend_bp.route("/") +@frontend_bp.route("/") +async def serve_spa(subpath: str = ""): + if subpath and (FRONTEND_DIST / subpath).is_file(): + return await send_from_directory(FRONTEND_DIST, subpath) + return await send_from_directory(FRONTEND_DIST, "index.html")