📝

FastAPI + Vite備忘録

2024/09/22に公開

FastAPI の静的アセットに Vite で作成・出力したページを採用したい、その開発は Vite 側で行いたいというケース。
特に複数アプリを採用したところでハマったので備忘録。

要点

FastAPI 側で app.mount(APP_URI, StaticFiles(directory=VITE_BUILD_DIR)) したいときの、 FastAPI / Vite それぞれの設定について

実行環境メモ

構成

  • バックエンドに FastAPI を採用
  • フロントエンドに Vite(React+TypeScript)採用
    • もともと HTML+JS で実装
      • app/assets/*, app/templates/* + templates.TemplateResponse() 構成
      • この過去の構成については触れない
    • 途中から「Vite のビルド出力ファイルを読み込み」に変更
      • app/dist/* + StatifFiles() 構成に変更
      • 更にアプリを追加し app/dist/app_a/*, app/dist/app_b/* + StatifFiles() 構成に変更 <- この場合の設定について

ディレクトリイメージ

関係ないファイルはほぼ非表示

$ tree
.
├── app
│   ├── dist
│   │   ├── app_a
│   │   │   ├── assets
│   │   │   │   ├── ...
│   │   │   └── index.html
│   │   └── app_b
│   │       ├── assets
│   │       │   ├── ...
│   │       └── index.html
│   ├── main.py
│   ├── ...
├── app_a
│   ├── public
│   │   └── assets
│   ├── src
│   │   ├── ...
│   └── vite.config.ts
├── app_b
│   ├── public
│   │   └── assets
│   ├── src
│   │   ├── ...
│   └── vite.config.ts
├── ...

目指す状態

開発環境において

  • 各アプリは app_a, app_b フォルダ内 Vite にて localhost:5173 で普通に開発したい
  • ビルド後に FastAPI にて localhost:8000/app_a, localhost:8000/app_b でそれぞれのアプリを見たい

実装

最低限必要なのは下記。

  • FastAPI 側
    • CORS 設定
    • パス解決
  • Vite 側
    • ビルド設定(出力後に移動すれば良いので必須ではない)
    • basename 設定

を済ませれば良い。

FastAPI

必要な箇所のみ

main.py

# CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# static assets
app.mount("/app_a", StaticFiles(directory="app/dist/app_a", html=True), name="app_a")
app.mount("/app_b", StaticFiles(directory="app/dist/app_b", html=True), name="app_b")

# fallback route(app_a)
@app.get("/app_a/{full_path:path}")
async def serve_app_a(full_path: str):
    return FileResponse("app/dist/app_a/index.html")

# fallback route(app_b)
@app.get("/app_b/{full_path:path}")
async def serve_app_b(full_path: str):
    return FileResponse("app/dist/app_b/index.html")

Vite

app_a 側設定。特に BrowserRouter > basename の必要性に気付かずかなりハマった。。

vite.config.ts

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  base: process.env.NODE_ENV === "production" ? "/app_a/" : "/", // ビルド時に生成される静的アセットの参照パス prefix
  build: {
    outDir: "../app/dist/app_a", // ビルドファイル出力先フォルダ
    assetsDir: "assets", // 静的アセットの出力先(=app_a/assetsに出力)
    emptyOutDir: true,
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

App.tsx

import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
...

const basename = import.meta.env.PROD ? "/app_a/" : "/"

const App = () => {
  return (
    <Router basename={basename}>
      <Routes>
        <Route path="/" element={...} />
        ...
    </Router>
  )
}

export default App

Discussion