📝
FastAPI + Vite備忘録
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()
構成に変更 <- この場合の設定について
-
- もともと HTML+JS で実装
ディレクトリイメージ
関係ないファイルはほぼ非表示
$ 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