はじめに
Web アプリ開発において、React(フロントエンド) と FastAPI(バックエンド) の組み合わせを使うことが個人的に増えてきました。しかし開発をしていると次のような点が面倒くさいなあと思うことがあります。
-
CORS(Cross-Origin Resource Sharing)問題の頻発
ブラウザのセキュリティ機構により、オリジン(スキーム+ドメイン+ポート)が異なるサーバーにアクセスするときは CORS 設定が必須になります。誤ると “No ‘Access-Control-Allow-Origin’ header” エラーが連発し、「動くまでに時間がかかる」 という定番のつまずきポイントになります。 -
複数コンテナ管理の複雑性
本番環境で React と FastAPI をそれぞれ別コンテナにすると、ビルド手順・環境変数・ログ管理 が 2 倍に膨らみます。 -
ネットワーク設定の煩雑さ
コンテナ間通信のために内部 DNS 名やネットワークセグメントを意識する必要があり、「ホスト名が解決できない」 などのネットワーク系トラブルが起きやすくなります。
本記事では、これらの課題を 「ビルド時統合」 という手法で対応した時のメモです。開発時は分離して効率を保ち、本番環境では単一コンテナで運用する ハイブリッド構成 を実現ができて便利そうに思いました
従来アーキテクチャの課題
一般的な構成の面倒くさいところ
┌─────────────────┐ HTTP Request ┌─────────────────┐
│ React SPA │ ─────────────────→ │ FastAPI │
│ localhost:3000 │ ←───────────────── │ localhost:8000 │
└─────────────────┘ CORS必要! └─────────────────┘
主な課題
-
CORS 設定が必須
- オリジンが異なるため、ブラウザは標準でリクエストをブロックします。
-
access-control-allow-*
ヘッダを手動で付与するか、開発時のみプロキシを挟むなどの対処が必要です。
-
コンテナ管理の複雑化
- React 用と FastAPI 用で 2 つの Dockerfile / Docker イメージが必要になります。
- コンテナ間のヘルスチェックや再起動ポリシーもそれぞれ設定する手間が増えます。
-
ネットワーク設定
-
frontend → backend
方向の内部通信ポートを開ける必要があります。 - ローカル開発環境では
localhost
とポート番号、クラウドでは Service Discovery とロードバランサを別々に考える必要があります。
-
-
デプロイの複雑性
- CI/CD を 2 つのサービスに対して順序良く実行し、依存関係が壊れないように管理しなければなりません。
- たとえば React 側の環境変数(API エンドポイント)は、FastAPI がデプロイされるまで確定しないため、デプロイ順序の制約 が発生します。
解決アプローチ:統合アーキテクチャ
設計思想
開発時はマイクロサービスのように疎結合で素早く、本番ではモノリスのようにシンプルで運用しやすく。
開発時:分離構成で開発効率を最大化
┌─────────────────┐ Proxy ┌─────────────────┐
│ Vite Dev │ ─────────→ │ FastAPI │
│ localhost:8502 │ │ localhost:8000 │
└─────────────────┘ └─────────────────┘
本番時:統合構成でシンプルに運用
┌─────────────────────────────────────┐
│ FastAPI Container │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Static Files│ │ API │ │
│ │ (React App) │ │ Endpoints │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘
開発中は npm run dev
のホットリロード速度を最大限活用し、本番では React のビルド成果物(静的 HTML / JS / CSS)を FastAPI がそのまま配信 します。こうすることで CORS が不要になり、コンテナも 1 つ で済みます。
実装詳細
1. プロジェクト構成
project/
├── frontend/ # React アプリケーション
│ ├── src/
│ ├── package.json
│ └── vite.config.ts # 重要:ビルド設定
├── backend/ # FastAPI アプリケーション
│ ├── api/
│ │ ├── app/
│ │ │ └── main.py # 重要:静的ファイル配信設定
│ │ ├── static/ # ビルド成果物の配置先
│ │ └── Dockerfile
│ └── docker-compose.yml
└── README.md
ポイント
- ビルド成果物(
dist/
)をbackend/api/static/
に吐き出し、FastAPI からそのまま読み込ませる。frontend
とbackend
を 同一リポジトリ に置くことでバージョン管理を一元化できる。
2. フロントエンド設定 — vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 8502,
/**
* 開発時のみ /v1 配下を FastAPI へプロキシ
* 本番では統合されるため不要
*/
proxy: {
'/v1': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/v1/, '/v1'),
},
},
},
build: {
/**
* 🔑 バックエンド配下にビルド成果物を直接出力
* これにより docker build 時点で静的ファイルを取り込める
*/
outDir: '../backend/api/static',
emptyOutDir: true,
},
});
3. バックエンド設定— main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
import os
app = FastAPI(title="FastAPI Microlith Template")
# 開発時(分離構成)にのみ必要。統合後は不要になる
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- API エンドポイント ---
@app.get("/v1/health")
async def healthcheck():
return {"status": "ok"}
# --- 静的ファイル(React SPA) ---
static_dir = "static"
if not os.path.exists(static_dir):
os.makedirs(static_dir)
# React Routerの履歴API対応
app.mount("/", StaticFiles(directory=static_dir, html=True), name="spa")
Tips
ルートパス / にマウントすると ルーティングを React Router に委譲 できます。
404 ページを SPA 内で処理したい場合、html=True オプションが必須です。
4. コンテナ設定 — docker-compose.yml
version: "3.8"
services:
api:
build:
context: ./api
ports:
- "8000:8000"
env_file:
- .env
volumes:
# ローカル開発ではホットリロード用にマウント
- ./api/static:/app/static
command: >
uvicorn app.main:app
--host 0.0.0.0
--port 8000
--reload
本番環境 では --reload を外し、
gunicorn -k uvicorn.workers.UvicornWorker を使うことでパフォーマンスを向上できます。
5. 開発・デプロイ ワークフロー
開発時
# 1. フロントエンド:ホットリロード用サーバー起動
cd frontend
npm run dev # → http://localhost:8502
# 2. バックエンド:API サーバー起動(別ターミナル)
cd backend
docker-compose up # → http://localhost:8000
デプロイ時
# 1. フロントエンドをビルドして静的ファイルをコピー
cd frontend
npm run build # → backend/api/static/ 以下に出力
# 2. 単一コンテナをビルド&起動
cd backend
docker compose build # ビルド成果物込みのイメージ生成
docker compose up -d # ポート 8000 で SPA + API 一体配信
このアーキテクチャのメリット
🎯 課題解決効果
CORS 問題の根本解決
- クライアントと API が 同一オリジン になるため、CORS 設定そのものが不要になります。
- セキュリティ面でも許可オリジンを広げる必要がなく、防御範囲が狭まります。
コンテナ管理の簡素化
- 本番環境では 単一コンテナ に集約できるため、監視対象も 1 つだけ。
ネットワーク設定の単純化
- 外部公開ポートは 8000 番のみでシンプルです。
- 内部通信が不要になるため、VPC 内のセキュリティグループ設定も減ります。
デプロイの一元化
- CI/CD が 1 ジョブ にまとまり、パイプライン YAML が短くなります。
🚀 開発体験の向上
-
ホットリロードの速さ
Vite の高速ビルド & HMR をフルに享受できます。
💰 運用コストの削減
-
インフラコスト
コンテナ数が減ることで単純にコストが削減できます。 -
管理コスト
ログ一元化・メトリクス統合が簡単になり、監視パイプラインの運用負荷も軽減。
考慮すべきポイント
デメリット・制約
スケーリングの制約
- フロントエンドとバックエンドを別々に水平スケールしたい場合、分離構成の方が柔軟です。
- 例:静的ファイルだけ CDN + S3 で配信し、API は Auto Scaling したいとき。
キャッシュ戦略の制約
- 静的ファイルとAPIで異なるキャッシュ設定が困難か?
適用推奨ケース
-
個人開発や中小規模アプリケーション
単一コンテナでも十分捌けるトラフィク量のとき。 -
少人数チーム
フルスタック 1〜3 名で回している場合、運用・学習コスト削減効果が大きいです。 -
PoC / スタートアップの MVP
“とにかく早く市場に出したい” フェーズでは、分離構成よりもリリースサイクルが短くなります。
まとめ
React + FastAPI 統合アーキテクチャは、モダン Web 開発における以下の観点で便利です。
- CORS の煩雑さ → 不要
- 複数コンテナ管理 → 単一コンテナで運用
- ネットワーク設定の複雑さ → ポート 8000 だけに集約
開発時の柔軟性を保ちつつ、本番環境ではシンプルさを追求できるのが最大の魅力です。
特に 中小規模プロジェクト や 運用リソースが限られているチーム では、従来の「完全分離型マイクロサービス」よりも実践的な選択肢になると思います。