Hwandji commited on
Commit
6a6737e
·
1 Parent(s): cfadf68

fix: Add SPA Static Files handler for HuggingFace deployment

Browse files

- Created SPAStaticFiles class to preserve API routes
- Updated main.py to use SPA handler
- Fixes 404 errors on frontend routes in HuggingFace Spaces

Files changed (2) hide show
  1. backend/main.py +7 -5
  2. backend/spa_static_files.py +34 -0
backend/main.py CHANGED
@@ -21,7 +21,6 @@ from dotenv import load_dotenv
21
  from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Depends
22
  from fastapi.middleware.cors import CORSMiddleware
23
  from fastapi.responses import JSONResponse
24
- from fastapi.staticfiles import StaticFiles
25
  from contextlib import asynccontextmanager
26
  from typing import List, Dict, Optional
27
  import asyncio
@@ -225,16 +224,19 @@ def get_websocket_manager() -> WebSocketManager:
225
  return saap_app.websocket_manager
226
 
227
  # =====================================================
228
- # STATIC FILES - Serve Vue.js Frontend
229
  # =====================================================
230
 
 
 
 
231
  # Mount static files for frontend (must be AFTER all API routes)
232
- # This serves the built Vue.js app
233
  import os
234
  frontend_dist = "/app/frontend/dist"
235
  if os.path.exists(frontend_dist):
236
- app.mount("/", StaticFiles(directory=frontend_dist, html=True), name="static")
237
- logger.info(f"✅ Static files mounted: {frontend_dist}")
238
  else:
239
  logger.warning(f"⚠️ Frontend dist directory not found: {frontend_dist}")
240
 
 
21
  from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Depends
22
  from fastapi.middleware.cors import CORSMiddleware
23
  from fastapi.responses import JSONResponse
 
24
  from contextlib import asynccontextmanager
25
  from typing import List, Dict, Optional
26
  import asyncio
 
224
  return saap_app.websocket_manager
225
 
226
  # =====================================================
227
+ # STATIC FILES - Serve Vue.js Frontend (SPA Mode)
228
  # =====================================================
229
 
230
+ # Import custom SPA Static Files handler
231
+ from spa_static_files import SPAStaticFiles
232
+
233
  # Mount static files for frontend (must be AFTER all API routes)
234
+ # SPAStaticFiles preserves API routes while serving Vue.js SPA
235
  import os
236
  frontend_dist = "/app/frontend/dist"
237
  if os.path.exists(frontend_dist):
238
+ app.mount("/", SPAStaticFiles(directory=frontend_dist, html=True), name="static")
239
+ logger.info(f"✅ SPA Static files mounted: {frontend_dist}")
240
  else:
241
  logger.warning(f"⚠️ Frontend dist directory not found: {frontend_dist}")
242
 
backend/spa_static_files.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SPA Static Files Handler for FastAPI
3
+ Serves Vue.js SPA while preserving API routes
4
+ """
5
+ from fastapi.staticfiles import StaticFiles
6
+ from starlette.responses import FileResponse
7
+ from starlette.types import Scope
8
+ import os
9
+
10
+
11
+ class SPAStaticFiles(StaticFiles):
12
+ """
13
+ Custom StaticFiles that serves index.html for non-API routes (SPA fallback)
14
+ while allowing API routes to pass through
15
+ """
16
+
17
+ async def get_response(self, path: str, scope: Scope):
18
+ """
19
+ Handle requests:
20
+ - API routes (/api/*) → 404 (let FastAPI handle)
21
+ - Static files (exist) → serve file
22
+ - Everything else → index.html (SPA routing)
23
+ """
24
+ # Let API routes pass through to FastAPI
25
+ if path.startswith("api/") or path.startswith("docs") or path.startswith("redoc") or path.startswith("openapi.json"):
26
+ # Return 404 to let FastAPI handle it
27
+ raise RuntimeError("Not found")
28
+
29
+ try:
30
+ # Try to serve the file
31
+ return await super().get_response(path, scope)
32
+ except Exception:
33
+ # File not found → serve index.html for SPA routing
34
+ return FileResponse(os.path.join(self.directory, "index.html"))