Spa fallback fastapi mount
Skill kjuhwa/skills-hub/skills/fastapi/spa-fallback-fastapi-mount
Self-correcting knowledge corpus for Claude Code — 9 stable shape clusters, bias-correction pipeline baked into contribution flow. 47 papers, 45 techniques, 1.1k skills.
npx -y skills add kjuhwa/skills-hub --skill spa-fallback-fastapi-mountAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Serve a built Vite/React SPA from FastAPI with asset mounting, catch-all routing, and path-traversal protection.
SKILL.md
2.6 KB, 485 tokens by cl100k_base, as published. Nobody here has run it
SPA fallback FastAPI mount
When to use
You have one FastAPI process that in some deployments (Docker, web mode) also serves the built SPA, and in other deployments (desktop sidecar, API-only) does not ship the SPA. The mount must be a no-op when the frontend directory is absent.
Steps
- Detect the built frontend at startup (e.g.
Path(__file__).resolve().parent.parent / "frontend"). Iffrontend_dir.is_dir()is false, return — keep API-only deployments clean. - Mount hashed assets from
frontend/assets/under/assetsusingStaticFiles. Vite emits content-hashed filenames here, so long-term caching is safe. - Register a catch-all route last:
@app.get("/{full_path:path}"). Because FastAPI resolves routes in registration order, all API routes must be added before this — register it only from inside_mount_frontendinvoked afterregister_routers(app). - Inside the catch-all, resolve the requested path under the frontend directory (
file_path = (frontend_dir / full_path).resolve()). Guard withfile_path.is_relative_to(frontend_dir)to reject path-traversal (../../etc/passwd). If the resolved file exists and is inside the frontend dir, serve it; otherwise serveindex.htmlso client-side routes like/voices,/settingswork. - Always return
index.htmlwithmedia_type="text/html"on the fallback — some static file helpers default toapplication/octet-streamfor unrecognized extensions.
Counter / Caveats
- If you mount before registering API routers, the catch-all will swallow
/api/...requests. Always mount last. is_relative_tois only available in Python 3.9+. If you need broader support, compareresolved.parts[: len(root.parts)] == root.parts.- Do not set
html=TrueonStaticFilesfor the whole frontend — it conflicts with the explicit catch-all and hides 404s. - When the frontend emits new assets, old hashed files remain in cache but
index.htmldoes not — make sureindex.htmlis served with short TTL (via your reverse proxy) so clients pick up new builds.
Source references: backend/app.py (_mount_frontend).