第16章:APIを同じドメイン配下に寄せてCORSを減らす🧹✨
この章は「CORSで時間が溶ける問題」を、入口(リバプロ)でまとめて解決する回だよ〜😇🚪➡️🏠 結論から言うと、ブラウザに“同一オリジン”だと思わせるのが最強です💪
1) 今日のゴール🎯✨
- フロントが呼ぶURLを
/api/...みたいな相対パスにする🧭 - 入口(Caddy / Traefik)が
/apiをAPIコンテナへ転送する🚚 - 結果:CORS設定がほぼ不要になって、余計な
OPTIONSも減ることが多い🫶
2) そもそもCORSって何がつらいの?😵💫🍝
✅ “同一オリジン”の定義(超重要)🧠
ブラウザ的には、オリジンが同じかどうかは scheme/host/port のセットで決まるよ。(MDN Web Docs) つまり…
http://localhost:5173(フロント)http://localhost:8787(API)
これ、ポートが違うので別オリジン → CORSの世界へようこそ🎉😭
✅ プリフライト(OPTIONS)が増える理由🧨
CORSが絡むと、ブラウザは「いきなり送っていい?」って **事前確認(プリフライト)**を飛ばすことがあるよ。
このプリフライトは OPTIONS で、Access-Control-Request-Method などのヘッダーが付くやつ。(MDN Web Docs)
さらに、リクエストヘッダーが “安全リスト(safelisted)” 以外だとプリフライトになりやすい、みたいな細かい条件もある(ややこしいやつ)😇🌀(MDN Web Docs)
3) 解決のアイデア:同じドメイン(同じオリジン)に寄せる🏠✨
ポイントはこれ👇
- フロントのJSが叩く先を
https://dev.localhost/api/...に統一 - APIは裏側で別コンテナでもOK(入口が転送してくれる)
図でイメージ📌
ブラウザ
|
| https://dev.localhost/ → フロントへ
| https://dev.localhost/api/... → APIへ
v
[ リバースプロキシ(入口) ]
| |
v v
[ frontコンテナ ] [ apiコンテナ ]
こうすると、ブラウザから見たアクセス先はずっと 同一オリジン(dev.localhost) なので、CORSで悩みにくくなる✌️😆
4) 手順:Caddyで /api をAPIへ流す🚀🍞
4-1. Caddyfile(基本形:/api を剥がしてAPIへ)🧩
Caddyには handle_path っていう「パスプレフィックスを剥がして処理する」便利機能があるよ。(Caddy Web Server)
dev.localhost {
# API: /api/* → apiコンテナへ(/api を剥がして渡す)
handle_path /api/* {
reverse_proxy api:8787
}
# フロント(例:Vite dev server)
handle {
reverse_proxy front:5173
}
}
handle_path /api/*:/apiを剥がしてAPIへ転送してくれる🧼(Caddy Web Server)reverse_proxyは基本、Hostをそのまま渡しつつX-Forwarded-*を良い感じに付けてくれる(自分でヘッダー盛り盛りにしなくてOK寄り)🙆♂️(Caddy Web Server)
4-2. フロント側のfetchを「相対パス」に直す🎯
ここが気持ちいいポイント!✨
// ✅ 同一オリジンに寄せる(CORSの悩みが激減)
const res = await fetch("/api/todos");
// ❌ これだと別オリジンになりがち(CORS地獄)
const res2 = await fetch("http://localhost:8787/todos");
5) 手順:Traefikでやるなら(概念だけでもOK)🚦🤖
Traefikは「Dockerラベルでルーティング」が得意なやつ。(doc.traefik.io) パスを剥がしたいときは StripPrefix ミドルウェアが定番だよ。(doc.traefik.io)
イメージ👇(細部は環境で変わるので“型”として見てね)
labels:
- traefik.http.routers.api.rule=Host(`dev.localhost`) && PathPrefix(`/api`)
- traefik.http.middlewares.api-stripprefix.stripprefix.prefixes=/api
- traefik.http.routers.api.middlewares=api-stripprefix
- traefik.http.services.api.loadbalancer.server.port=8787
6) うまくいったか確認する方法🕵️♀️✅
✅ ブラウザDevToolsで見る(いちばん確実)🔍
- Networkタブで APIリクエストを見る
OPTIONSが消えた/減った → めっちゃ勝ち(ケースによるけど)🫶(MDN Web Docs)- Request URL が
https://dev.localhost/api/...になってるか確認👀
✅ Caddyログを見る👂
dev.localhostに来たログが出て/api/...が api 側に流れてたらOK👍
7) よくあるミス集(ここで詰まりやすい)🧯📕
🧨 ミス1:/api を剥がす/剥がさないのズレ
handle_pathは 剥がす (Caddy Web Server)- API側が
/api/todosを期待してる設計なら、剥がすと 404 になる😇
👉 対処:
- APIが
/todosを期待:handle_path /api/*(剥がす) - APIが
/api/todosを期待:handle /api/*(剥がさない)にする手もある(章18で設計練習するやつ)
🧨 ミス2:フロントがまだ http://localhost:8787 を叩いてる
入口でまとめても、コードが直ってなければCORS発生🤦♂️
👉 fetch("/api/...") に寄せる!
🧨 ミス3:502(Bad Gateway)
だいたいこれ👇
- APIが落ちてる / ポート違い / コンテナ名違い
- 同じネットワークにいない(Composeのネットワーク設計が原因)
8) ここ、ちょっとだけ注意🍪🧷(次章への伏線)
APIが同一オリジンになると、Cookieが自然に飛ぶようになるので、ログイン系は楽になる反面、CSRF対策の話が気になってくる(これは次章でちゃんとやるよ)🍪💣
9) AIに頼むと爆速になる質問テンプレ🤖⚡
✅ Caddyfileを作らせる
- 「
dev.localhostで/api/*はapi:8787、それ以外はfront:5173に流す Caddyfile を書いて。/apiは剥がして」
✅ 404/502の切り分けを手伝わせる
- 「Caddyで
/api/*をapi:8787に流してるのに 502。原因候補を“確認コマンド付き”で優先順に出して」
✅ フロントのAPI呼び出しを置換
- 「
http://localhost:8787を叩いてる箇所を、環境変数なしでも動くfetch('/api/...')に置き換える方針で修正案を出して」
10) ミニ課題🧪✨(15〜30分)
課題A:CORSを“消す”成功体験🎉
- Caddyfile に
handle_path /api/* { reverse_proxy api:8787 }を追加(Caddy Web Server) - フロントの
fetchを/api/...に統一 - DevToolsで
OPTIONSがどう変わったか観察する(MDN Web Docs)
課題B:わざと壊して直す🧯
handle_pathをhandleに変えて挙動の違いを見る(404になるなら理由を言語化✍️)
必要なら、この章の「完成形サンプル」として、front(Vite) + api(Node) + caddy の最小Compose一式も、コピペで動く形にまとめて出せるよ🐳📦✨