Static File Serving
FastAPI can serve static files (CSS, JS, images, SPAs) using Starlette's StaticFiles mount. For production, always offload static file serving to Nginx or a CDN — but having it in FastAPI is useful for development and simple deployments.
Learning Focus
By the end of this lesson you can: mount a static files directory, serve a single-page application with fallback routing, and configure cache headers.
Mounting a Static Directory
app/main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
# Mount /static → ./public directory
app.mount("/static", StaticFiles(directory="public"), name="static")
public/
├── css/
│ └── style.css
├── js/
│ └── app.js
└── images/
└── logo.png
# Access static files
curl http://localhost:8000/static/css/style.css
curl http://localhost:8000/static/images/logo.png
Serving a Single-Page Application (SPA)
SPAs handle routing client-side, so the server must return index.html for all unknown paths:
app/main.py
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pathlib import Path
FRONTEND_DIR = Path("frontend/dist")
app = FastAPI()
# Mount static assets
app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets")
# Catch-all for SPA routing
@app.get("/{full_path:path}", include_in_schema=False)
async def spa_fallback(full_path: str) -> FileResponse:
return FileResponse(FRONTEND_DIR / "index.html")
warning
Place the SPA catch-all route last and mark it with include_in_schema=False to exclude it from OpenAPI docs.
URL Generation for Static Files
app/routers/templates.py
from fastapi import Request
@router.get("/page")
async def page(request: Request) -> dict:
css_url = request.url_for("static", path="css/style.css")
return {"css": str(css_url)}
# → "http://localhost:8000/static/css/style.css"
Common Pitfalls
| Pitfall | Cause / Symptom | Fix |
|---|---|---|
StaticFiles route blocking API routes | Mount path overlaps with API prefix | Mount static files at a unique prefix like /static |
| 404 for SPA deep links | No catch-all route | Add /{full_path:path} fallback returning index.html |
| Files not updating | Browser caches aggressively | Use content-hashed filenames in build tools (Vite/webpack) |
| Large files slow down FastAPI | Python serving files instead of Nginx | In production, serve static files from Nginx, not FastAPI |
| Directory listing enabled | StaticFiles(html=True) | Use html=False (default) unless you explicitly want directory listing |
Hands-On Practice
setup-static.sh
mkdir -p public/css public/js
cat > public/css/style.css << 'EOF'
body { font-family: sans-serif; background: #f5f5f5; }
EOF
cat > public/js/app.js << 'EOF'
console.log("FastAPI static demo");
EOF
uvicorn app.main:app --reload
curl http://localhost:8000/static/css/style.css
curl http://localhost:8000/static/js/app.js