本文 AI 產出,尚未審核

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 本身不負責壓縮,但配合 StarletteCompressionMiddleware 可以輕鬆完成。

from starlette.middleware.compression import CompressionMiddleware

app.add_middleware(CompressionMiddleware, minimum_size=500)  # 超過 500 bytes 才壓縮

5️⃣ 多個靜態目錄的情境

有時候會同時提供 前端資產(如 Vue、React 打包結果)與 第三方套件的靜態檔(如 bootstrapfontawesome)。只要分別掛載不同的 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='...')

最佳實踐

  1. 統一管理路徑:建立 config.py,將 STATIC_DIRTEMPLATE_DIR 等設定集中管理。
  2. 啟用壓縮與快取:在生產環境加上 CompressionMiddlewarecache_control
  3. 分層目錄結構static/cssstatic/jsstatic/images,保持資源分類清晰。
  4. 使用版本化檔名(如 app.1.2.3.js)或 query string?v=123)避免瀏覽器緩存舊檔。
  5. 自動化測試:利用 TestClient 確認 /static/... 能正確回傳 200 且 Content-Type 正確。

實際應用場景

📦 1. 企業內部儀表板

一個企業內部的管理系統,需要快速呈現圖表與即時資料。前端使用 Chart.jsBootstrap,所有 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()directorypackages 參數靈活實現。

掌握以上概念與實務技巧,你就能在 FastAPI 專案中自信地提供穩定、快速的前端資源,讓 API 與 UI 並肩前進,打造完整的全端應用!祝開發順利 🎉