FastAPI 教學:靜態檔案與模板(Static & Templates)— StaticFiles 設定
簡介
在建置 Web 應用程式時,除了 API 本身的邏輯,前端資源(如 CSS、JavaScript、圖片)與 模板檔(HTML)同樣是不可或缺的組件。FastAPI 以 ASGI 為核心,天然支援非阻塞 I/O,但若不正確設定靜態檔案的路徑,前端資源將無法被正確載入,使用者體驗會大打折扣。
本篇文章聚焦於 StaticFiles 的設定與最佳實踐,從最基本的掛載方式,到結合 Jinja2 模板引擎的完整範例,讓你能在 FastAPI 專案中快速、穩定地提供靜態資源。即使是剛接觸 FastAPI 的新手,也能透過清晰的步驟,建立可維護的前後端分離架構。
核心概念
1️⃣ 為什麼需要 StaticFiles?
FastAPI 內部使用 Starlette 作為底層框架,而 Starlette 提供了 StaticFiles 中介軟體,用來將檔案系統中的目錄映射為 HTTP 路徑。這樣一來,瀏覽器請求 /static/css/style.css 時,就會直接從本機磁碟讀取對應檔案並回傳,而不必經過額外的路由函式。
重點:
StaticFiles只負責檔案的讀取與快取,不會執行任何程式邏輯,因此效能相當高。
2️⃣ 基本掛載方式
在 FastAPI 中掛載靜態目錄,只需要在 FastAPI 實例上呼叫 mount(),傳入 URL 前綴與 StaticFiles 物件。
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
# 將本機的 ./static 目錄掛載到 /static 路徑
app.mount("/static", StaticFiles(directory="static"), name="static")
/static:客戶端請求的 URL 前綴。directory="static":實際的檔案系統路徑,建議使用相對於專案根目錄的路徑,避免硬編碼絕對路徑。name="static":在url_for產生 URL 時使用的名稱(可選)。
3️⃣ 與 Jinja2 模板結合
靜態檔案往往與 HTML 模板一起使用。FastAPI 內建 Jinja2Templates,可以在模板中使用 url_for("static", path="...") 產生正確的靜態資源 URL。
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get("/")
async def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
index.html(放在 templates/)範例:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>FastAPI 靜態檔案示例</title>
<!-- 使用 url_for 產生靜態檔案路徑 -->
<link rel="stylesheet" href="{{ url_for('static', path='css/style.css') }}">
</head>
<body>
<h1>Hello, FastAPI!</h1>
<img src="{{ url_for('static', path='images/logo.png') }}" alt="Logo">
<script src="{{ url_for('static', path='js/app.js') }}"></script>
</body>
</html>
技巧:在開發階段,建議將靜態檔案放在
static/,而非public/或其他名稱,這樣能避免與Starlette內建的StaticFiles預設路徑衝突。
4️⃣ 進階設定:快取、壓縮與自訂 MIME 類型
StaticFiles 支援以下參數,讓你在生產環境中提升效能:
| 參數 | 說明 | 預設值 |
|---|---|---|
html |
當請求路徑指向目錄且該目錄內有 index.html 時,自動回傳該檔案 |
False |
check_dir |
啟動時檢查目錄是否存在,避免部署時因路徑錯誤導致 404 | True |
packages |
直接從已安裝的 Python 套件中載入靜態檔案(如 starlette.staticfiles) |
None |
cache_control |
設定 Cache-Control 標頭,支援 max_age(秒)與 public/private |
None |
app.mount(
"/static",
StaticFiles(
directory="static",
html=True, # 支援自動尋找 index.html
check_dir=True,
cache_control={"max_age": 3600, "public": True} # 1 小時快取
),
name="static",
)
若需要 gzip 壓縮,可以在前端使用 CDN 或在 ASGI 伺服器(如 uvicorn)上加上 --compression 參數;StaticFiles 本身不負責壓縮,但配合 Starlette 的 CompressionMiddleware 可以輕鬆完成。
from starlette.middleware.compression import CompressionMiddleware
app.add_middleware(CompressionMiddleware, minimum_size=500) # 超過 500 bytes 才壓縮
5️⃣ 多個靜態目錄的情境
有時候會同時提供 前端資產(如 Vue、React 打包結果)與 第三方套件的靜態檔(如 bootstrap、fontawesome)。只要分別掛載不同的 URL 前綴即可:
# 前端打包產物
app.mount("/assets", StaticFiles(directory="frontend/dist"), name="assets")
# 第三方套件(直接從套件內部載入)
app.mount(
"/vendor",
StaticFiles(packages=[("bootstrap", "bootstrap/dist")]),
name="vendor",
)
在模板中使用:
<link rel="stylesheet" href="{{ url_for('vendor', path='css/bootstrap.min.css') }}">
<script src="{{ url_for('assets', path='js/main.js') }}"></script>
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 路徑錯誤 | directory 使用絕對路徑或相對路徑不一致,部署時找不到檔案。 |
使用 Path(__file__).parent / "static" 取得絕對路徑,或在 pyproject.toml 中設定 package_dir。 |
未加 check_dir=False |
開發時目錄不存在會直接拋出例外,導致應用程式無法啟動。 | 在測試環境保留 check_dir=True,正式環境可視需求關閉。 |
| Cache-Control 未設定 | 靜態資源每次都被重新下載,效能低下。 | 在 StaticFiles 中加入 cache_control,或使用 CDN 做長期快取。 |
| 同名路由衝突 | app.mount("/static", ...) 與自訂路由 /static/{item_id} 產生衝突。 |
盡量將靜態路徑放在最前或最末,或改用不同前綴(如 /public)。 |
未使用 url_for |
硬編碼靜態檔案路徑,導致部署子路徑(sub‑path)時失效。 | 在模板或前端程式中統一使用 url_for('static', path='...')。 |
最佳實踐:
- 統一管理路徑:建立
config.py,將STATIC_DIR、TEMPLATE_DIR等設定集中管理。 - 啟用壓縮與快取:在生產環境加上
CompressionMiddleware與cache_control。 - 分層目錄結構:
static/css、static/js、static/images,保持資源分類清晰。 - 使用版本化檔名(如
app.1.2.3.js)或 query string(?v=123)避免瀏覽器緩存舊檔。 - 自動化測試:利用
TestClient確認/static/...能正確回傳 200 且Content-Type正確。
實際應用場景
📦 1. 企業內部儀表板
一個企業內部的管理系統,需要快速呈現圖表與即時資料。前端使用 Chart.js、Bootstrap,所有 CSS/JS 放在 static/,而儀表板的 HTML 使用 Jinja2 動態渲染。透過 StaticFiles 的快取設定,使用者每次切換頁面時,只會重新取得一次圖表資料,靜態資源則直接從瀏覽器快取讀取。
🌐 2. 公開 API 文件站
很多 API 會提供一個「文件站」供開發者閱讀。這類站點通常是 純靜態 HTML(由 MkDocs、Sphinx 產生),但仍想與 FastAPI 本身共用同一個 ASGI 應用。只要把產生的 site/ 目錄掛載為 /docs:
app.mount("/docs", StaticFiles(directory="site", html=True), name="docs")
使用者直接訪問 https://example.com/docs/ 即可看到完整文件,且不需要額外的 Nginx 配置。
🛒 3. 電商平台的商品圖片服務
電商平台會有大量商品圖片,通常會放在雲端儲存(如 S3),但在開發或測試環境,會先放在本機的 static/images/products。掛載方式不變,只要在部署腳本中切換 directory 指向雲端掛載點即可,保持程式碼不變。
總結
StaticFiles是 FastAPI(底層 Starlette)提供的 高效靜態資源服務,只要正確掛載目錄,就能讓前端檔案快速回傳。- 透過
Jinja2Templates搭配url_for('static', path='...'),可以在模板中安全且彈性地引用靜態資源,避免硬編碼路徑。 - 快取、壓縮與版本化 是提升生產環境效能的關鍵設定;同時要注意路徑管理與避免路由衝突。
- 多靜態目錄、套件內部資源以及 CDN 整合,都可以透過
mount()的directory、packages參數靈活實現。
掌握以上概念與實務技巧,你就能在 FastAPI 專案中自信地提供穩定、快速的前端資源,讓 API 與 UI 並肩前進,打造完整的全端應用!祝開發順利 🎉