Skip to main content

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

PitfallCause / SymptomFix
StaticFiles route blocking API routesMount path overlaps with API prefixMount static files at a unique prefix like /static
404 for SPA deep linksNo catch-all routeAdd /{full_path:path} fallback returning index.html
Files not updatingBrowser caches aggressivelyUse content-hashed filenames in build tools (Vite/webpack)
Large files slow down FastAPIPython serving files instead of NginxIn production, serve static files from Nginx, not FastAPI
Directory listing enabledStaticFiles(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

What's Next