先に要点
フロントエンドから API を呼んだら CORS エラーが出たけど、何が悪いのか分からない というのはかなりよくあるつまずきです。
しかも、初心者のうちは サーバーエラー と ブラウザの制約 が混ざって見えやすいので、さらに分かりにくくなります。
この記事では、2026年4月4日時点で MDN の CORS ガイド、Preflight request の説明、WHATWG Fetch Standard の CORS まわりを確認しながら、CORS とは何か、なぜエラーになるのか、どう考えると整理しやすいのかを初心者向けにまとめます。
フロントエンドと API を分ける構成の全体像から見たいなら、代表的なフレームワーク7選|Laravel・Django・Rails・Spring Boot・Next.js・Nuxt・FastAPIの向いている用途を比較 もつながりやすいです。
CORSとは何か
CORS は Cross-Origin Resource Sharing の略で、ブラウザが別オリジンへのリクエストをどう許可するかを決める仕組みです。
MDN でも、追加の HTTP ヘッダーによって、あるオリジンで動く Web アプリが別オリジンのリソースへアクセスできるかをブラウザへ伝える仕組みと説明されています。
ここで先に押さえたいのは、CORS はブラウザの仕組み だということです。 つまり、curl やサーバー間通信では通るのに、ブラウザだけ失敗することがあります。
次の図では、同一オリジン・別オリジン(単純リクエスト)・別オリジン(プリフライト)の3パターンで、ブラウザとサーバーの間で何が起きているかをステップごとに確認できます。
そもそも「オリジン」とは何か
Origin は、ざっくり言うと スキーム + ホスト + ポート の組み合わせです。
たとえば、
https://example.comhttps://api.example.comhttp://example.comhttps://example.com:8443
は、それぞれ別オリジンとして扱われることがあります。
このため、
- フロントは
http://localhost:3000 - API は
http://localhost:8000
のような開発構成でも、ブラウザから見ると別オリジンです。
ここで CORS が出やすくなります。
なぜそんな制限があるのか
背景にあるのは Same-Origin Policy です。
これは、あるサイト上で動くスクリプトが、別オリジンの応答を自由に読めないようにするブラウザの基本制約です。
もしこれがなければ、悪意あるサイトを開いただけで、別サイトの情報が勝手に読まれる危険が高くなります。
そのため、同一オリジン以外は原則そのまま読ませない をベースにして、必要なときだけ CORS で許可する形になっています。
どういうときに CORS エラーになるのか
初心者がハマりやすいのは、リクエスト自体は飛んでいるのに、ブラウザでだけ読めない パターンです。
たとえば次のような場面です。
http://localhost:3000の画面からhttp://localhost:8000/apiを呼ぶhttps://app.example.comからhttps://api.example.comを呼ぶ- JavaScript の
fetch()や Axios で別オリジンの API を叩く
このとき、サーバーが適切な CORS ヘッダーを返していないと、ブラウザは応答をアプリ側へ渡しません。
まずよくある誤解
API が 200 を返していても CORS で失敗することがある
ここはかなり大事です。
ネットワークタブを見ると 200 に見えるのに、JavaScript 側ではエラーになることがあります。
これは、HTTP 応答そのものは返っていても、ブラウザが このオリジンには読ませない と判断しているからです。
つまり、バックエンド処理と CORS 判定は別の話です。
「サーバー間通信」には CORS は関係ない
サーバーからサーバーへ API を呼ぶだけなら、普通は CORS は関係しません。
ブラウザで動く JavaScript が別オリジンへ取りに行くときに問題になりやすいです。
no-cors を付ければ解決するわけではない
fetch() の mode: 'no-cors' を見つけて、これで解決できると思う人も多いです。
ただ、このモードは 読める普通の API 応答を得る ための解決策ではありません。
初心者が 画面から API の JSON を受け取りたい 場面では、ほぼ期待どおりに使えないと考えた方が安全です。
Preflight request とは何か
ブラウザは、いきなり本番のリクエストを送る前に、このリクエスト送ってよい? と確認することがあります。
これが Preflight request です。
典型的には、
Content-Type: application/jsonを付けるAuthorizationヘッダーを付けるPUTPATCHDELETEを使う
ような場面で、先に OPTIONS リクエストが飛びます。
この preflight に対してサーバーが正しく返せないと、実際の本リクエストまで進みません。
初心者が POST なのに OPTIONS が見える と戸惑いやすいのはこのためです。
何を許可するのか
CORS では、主に次を整理します。
- どの Origin からのアクセスを許可するか
- どの HTTP メソッドを許可するか
- どのヘッダーを許可するか
- Cookie や認証情報を含めるか
特によく見るのが Access-Control-Allow-Origin です。
これは、どのオリジンを許可するかをブラウザへ伝えるヘッダーです。
初心者向けの考え方
実務では、CORS を有効にする ではなく、どの画面からどの API を呼ばせたいのか を先に決めます。
たとえば、
- フロント:
https://app.example.com - API:
https://api.example.com
なら、API 側で https://app.example.com を許可対象にします。
ここで雑に * を返すと、開発中は動いても、あとで認証や Cookie を含めたときに困りやすいです。
特に credentials: 'include' を使う構成では、ワイルドカードで済まないことがあります。
つまずきやすい原因
ポートが違う
localhostが同じでも、ポートが違えば別オリジン。ローカル開発でかなり多いです。
OPTIONSを処理していない
POSTは実装済みでもpreflight用のOPTIONS応答がなく、ブラウザだけ止まるパターンです。
許可オリジンの設定ミス
ワイルドカード(*)にしすぎてcredentialsとぶつかる、本番ドメインを入れ忘れるなど。
CORSと認証エラーが混ざる
401/403とCORSが同時に出ると原因が見えにくい。ネットワークタブで分けて見ます。
1. フロントと API のポートが違う
ローカル開発ではこれがかなり多いです。
localhost が同じでも、ポートが違えば別オリジンになることがあります。
2. preflight の OPTIONS を処理していない
本体の POST /api/... は作ったのに、OPTIONS への返しが足りず、ブラウザだけ止まるパターンです。
3. 許可オリジンを雑にしすぎる or 絞りすぎる
* にしてしまってあとで認証情報とぶつかる、逆に本番ドメインを入れ忘れる、というのはかなりよくあります。
4. CORS と認証エラーが混ざって見える
401 や 403 と CORS が同時に出ると、どちらが本当の原因か見えにくくなります。
まずはネットワークタブで、サーバー応答そのもの と ブラウザの CORS 判定 を分けて見るのが大事です。
どう確認するとよいか
初心者向けには、次の順で見ると整理しやすいです。
- リクエスト元の画面URLを確認する
- 呼んでいる API の URL を確認する
- オリジンが違うかを見る
- ネットワークタブで
OPTIONSが飛んでいないか見る - 応答ヘッダーに
Access-Control-Allow-Originなどがあるか見る - 401/403/500 と CORS を混同していないか見る
この順に分けるだけで、かなり迷いにくくなります。
実務でどう考えるか
実務では、CORS は とりあえず動けばよい設定 にしない方が安全です。
フロントと API を分ける構成ではかなり普通に出てくるので、最初から 許可するオリジン を設計に入れておく方が後で崩れにくいです。
特に次のような場面で重要です。
まとめ
CORS は、ブラウザが別オリジンのリクエストをどう扱うかを決める仕組みです。
大事なのは、CORS エラーは API が壊れた のではなく、ブラウザが読ませていない ことが多いと分けて考えることです。
そのうえで、Origin、Same-Origin Policy、Preflight request を押さえると、かなり見通しがよくなります。
初心者のうちは、どの画面からどの API を呼ばせたいのか を先に整理して、サーバー側で必要な範囲だけ許可する考え方で進めるのがいちばん分かりやすいです。
参考情報
- MDN: Cross-Origin Resource Sharing (CORS)
- MDN: Preflight request
- WHATWG Fetch Standard: CORS protocol