fix: serve frontend in production — multi-stage Dockerfile with Node build + StaticFiles mount

- Dockerfile: add Node.js frontend build stage (vite build)
- Dockerfile: copy frontend/dist into runtime image
- app/main.py: mount /assets static files + SPA catch-all fallback
- app/main.py: serve index.html for all non-API routes
- Fixes: https://crm.media-on.de/ returning 404 (frontend not served)
This commit is contained in:
leocrm-bot
2026-07-02 09:55:50 +02:00
parent 5b7b1575de
commit f477efc366
2 changed files with 50 additions and 19 deletions
+25 -1
View File
@@ -6,9 +6,12 @@ import time
import traceback
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
import os
from app.config import get_settings
from app.core.db import close_engine, get_engine
@@ -123,6 +126,27 @@ def create_app() -> FastAPI:
app.include_router(ai_copilot.router)
app.include_router(workflows.router)
# ─── Serve frontend static files (SPA) ───────────────────────────────
# Mount built frontend assets (JS, CSS, images)
frontend_dist = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist")
if os.path.isdir(frontend_dist):
# Serve static assets at /assets
assets_path = os.path.join(frontend_dist, "assets")
if os.path.isdir(assets_path):
app.mount("/assets", StaticFiles(directory=assets_path), name="assets")
# SPA catch-all: serve index.html for all non-API routes
@app.get("/{full_path:path}")
async def spa_spa(full_path: str):
"""Serve index.html for all non-API routes (SPA fallback)."""
# Don't intercept API routes
if full_path.startswith(("api/", "docs", "openapi", "redoc")):
raise HTTPException(status_code=404, detail="Not Found")
index_path = os.path.join(frontend_dist, "index.html")
if os.path.isfile(index_path):
return FileResponse(index_path)
raise HTTPException(status_code=404, detail="Frontend not built")
return app