JavaScript Access to fetch has been blocked by CORS policy の原因と解決方法【fetch APIでの3つの対処法】

JavaScript CORSエラーとは?ざっくりと3行で
  • CORSエラーは、ブラウザから別オリジンのAPIにfetchでアクセスした際、サーバー側の応答に許可情報がないと発生するエラーだ。
  • 実務ではサーバーがAccess-Control-Allow-Originヘッダーを返していないケースが原因の大半を占める。
  • 解決にはNetworkタブでレスポンスヘッダーを確認し、サーバー側でCORS設定を追加するか、開発環境ではプロキシを使えばいい。
JavaScriptのfetchでCORSエラーが発生し、サーバーにAccess-Control-Allow-Originヘッダーを追加して解決する4コマ漫画
①外部APIをfetchで呼び出し実行しようとする。②コンソールに真っ赤なCORSエラーが表示され戸惑う。③デプロイ太郎がサーバー側のヘッダー不足を指摘する。④ヘッダーを追加し再テストすると通信が成功する。

漫画で描かれているように、CORSエラーはブラウザの開発者コンソールに突然赤い文字で表示されるため、多くの開発者が最初は原因をつかめず動揺します。しかし実際にはサーバーがAccess-Control-Allow-Originヘッダーを返していないという単純な原因であるケースが大半を占めています。

厄介なのは、このエラーがNetworkタブ上ではリクエストが成功しているように見える点です。サーバー側の処理自体は正常に完了しているのに、レスポンスを受け取ったブラウザだけが同一オリジンポリシーに基づいて読み取りを拒否します。原因の切り分けを誤ると、サーバーのログを延々と調べて時間を浪費することになりかねません。

放置したまま本番環境にリリースすると、ユーザーの画面ではデータが一切表示されないまま真っ白になったり、フォーム送信が静かに失敗したりする恐れがあります。開発初期の段階でCORS設定を確認する習慣をつけておくことが、こうした障害を未然に防ぐ近道になります。

JavaScript CORSエラーの基本情報

JavaScript CORSエラーは、ブラウザのfetch APIやXMLHttpRequestで別オリジンのAPIにアクセスした際、サーバーの応答に必要なヘッダーが含まれていないためにレスポンスの読み取りが拒否される現象です。エラーメッセージの先頭はAccess to fetchで始まるのが特徴です。

項目内容
エラーメッセージAccess to fetch at ‘(リクエスト先URL)’ from origin ‘(自分のオリジン)’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
発生する言語・環境JavaScript(ブラウザ)/Chrome・Edge等のChromiumベースブラウザ、Fetch APIやXMLHttpRequestを使った通信全般
エラーの意味指定したURLへのfetchが、CORSポリシーによってブロックされました。要求したリソースに’Access-Control-Allow-Origin’ヘッダーが存在しません、という意味。ブラウザの同一オリジンポリシーがレスポンスの読み取りを拒否したことを示す。
主な原因アクセス先サーバーがAccess-Control-Allow-Originヘッダーを返していないこと
解決の基本方針Networkタブでレスポンスヘッダーを確認し、サーバー側にCORS設定を追加するか、開発環境ではプロキシ機能を使う

なぜJavaScriptでCORSエラー(has been blocked by CORS policy)が発生するのか?

CORSエラーは実はサーバーではなくブラウザ側の防御機構によって起きるエラーです。curlやPostmanで同じURLを叩くと正常にレスポンスが返ってくることが多く、サーバーの処理自体は成功しているのにブラウザだけがレスポンスの読み取りを拒否している、というケースが少なくありません。

CORSエラーの原因は、リクエストが処理される流れのどこで許可情報が欠けているかによって整理できます。以下では発生しやすい3つの箇所を、実務での遭遇頻度が高い順に見ていきます。

レスポンスにAccess-Control-Allow-Originヘッダーがない

もっとも多いのは、アクセス先のサーバーがCORSに関する設定を何も行っておらず、レスポンスにAccess-Control-Allow-Originヘッダーが含まれていないケースです。

// フロントエンド (Vite 5 + React 18) - http://localhost:5173 で動作
fetch('https://api.example.com/users')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));
// このfetch自体は正しく書けているが、次のサーバー側の設定が原因でブロックされる

// バックエンド (Node.js 20 + Express 4) - CORS関連の設定を何も行っていない
const express = require('express');
const app = express();

app.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'Taro' }]);
  // Access-Control-Allow-Origin ヘッダーを一切返していないため
  // ブラウザ側でレスポンスの読み取りがブロックされる
});

app.listen(4000);

サーバー側にcorsパッケージを導入し、許可するオリジンを明示するだけでAccess-Control-Allow-Originヘッダーが自動的に付与されるようになります。フロントエンドのコードは変更不要です。

// バックエンド (Node.js 20 + Express 4 + corsパッケージ)
const express = require('express');
const cors = require('cors');
const app = express();

// 許可するオリジンを明示し、Access-Control-Allow-Originを自動付与する
app.use(cors({ origin: 'http://localhost:5173' }));

app.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'Taro' }]);
});

app.listen(4000);

フロントとバックエンドでポート番号が違う開発環境

開発環境ではlocalhost同士で通信していても、ポート番号が異なるだけで別オリジンとみなされます。ホスト名が同じだから大丈夫、という思い込みでハマりやすいパターンです。

// フロントエンド (Vite 5) は http://localhost:5173 で起動
// バックエンドAPIは http://localhost:8080 で起動している
fetch('http://localhost:8080/api/profile')
  .then(res => res.json())
  .then(data => setProfile(data));
// ホスト名が同じlocalhostでも、ポート番号が異なると別オリジン扱いになる

開発環境ではサーバー側を直接呼ばず、ビルドツールのプロキシ機能を使ってフロントから見た通信先を同一オリジンに揃える方法がよく使われます。

// vite.config.js にプロキシを設定し、フロントから見ると同一オリジンにする
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      },
    },
  },
};

// fetch側はプロキシ経由の相対パスに書き換える
fetch('/api/profile')
  .then(res => res.json())
  .then(data => setProfile(data));

プリフライトリクエスト(OPTIONS)がブロックされている

PUTDELETEなどのメソッド、またはAuthorizationのようなカスタムヘッダーを使うと、ブラウザは本リクエストの前にOPTIONSメソッドでプリフライトリクエストを自動送信します。このプリフライトへの応答が不足しているとエラーになります。

// PUTメソッドとAuthorizationヘッダーを使うため
// ブラウザは本リクエストの前にOPTIONSでプリフライトを自動送信する
fetch('https://api.example.com/orders/1', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer abc123',
  },
  body: JSON.stringify({ status: 'shipped' }),
});
// サーバーがOPTIONSに未対応、またはAccess-Control-Allow-Methods /
// Access-Control-Allow-Headers が不足していると、プリフライトの時点でブロックされる

corsパッケージでメソッドと許可ヘッダーを明示すれば、プリフライトへの応答も自動化されます。

// バックエンド (Node.js 20 + Express 4 + corsパッケージ)
const cors = require('cors');

app.use(cors({
  origin: 'https://example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));
// corsミドルウェアがOPTIONSへの応答と各ヘッダーの付与を自動化する

app.put('/orders/:id', (req, res) => {
  res.json({ status: 'updated' });
});
ITKAGYO運営者デプロイ太郎のアイコン画像

このエラー、赤字で表示されるだけに焦りますよね。でも落ち着いてNetworkタブを見れば、意外とシンプルな原因であることが多いです。

CORSエラーはブラウザによって表示されるメッセージが違う?

JavaScript CORSエラーの文言はブラウザエンジンによって微妙に異なります。原因は同じでも表示されるテキストが違うため、検索する際は自分のブラウザに合った文言で調べると情報にたどり着きやすくなります。

実行環境表示されるエラーメッセージ
Chrome・Edge(Chromiumベースブラウザ)Access to fetch at ‘(URL)’ from origin ‘(オリジン)’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
FirefoxCross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at (URL). (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: (ステータスコード).
Safari(WebKitベース)[Error] Origin (オリジン) is not allowed by Access-Control-Allow-Origin.

ReactやNext.jsでCORSエラーが起きやすいのはなぜ?

Reactでの発生パターン

Create React AppやViteで作ったReactアプリは、開発サーバーとAPIサーバーが別プロセスで動くため、コンポーネントから外部APIへ直接fetchするとCORSエラーに遭遇しやすくなります。開発時はビルドツールのプロキシ機能を使うのが定番です。

// Create React App (react-scripts 5) の場合は package.json に proxy を追加するだけでよい
// package.json
// {
//   "proxy": "http://localhost:8080"
// }

// コンポーネント側は相対パスでfetchする
useEffect(() => {
  fetch('/api/profile')
    .then(res => res.json())
    .then(data => setProfile(data));
}, []);
開発中はプロキシで回避できても、本番ビルドではフロントの配信ドメインとAPIのドメインが実際に分かれることが多いため、公開前に本番相当のオリジンでも動作確認しておくと安全です。

Next.jsでの発生パターン

Next.jsではRoute Handler(旧API Routes)を経由して外部APIを呼び出すと、サーバー同士の通信になるためブラウザのCORS制約自体を受けなくなります。フロントから直接外部APIを叩いて詰まっている場合の定番の回避策です。

// Next.js 14 (App Router) の Route Handler を経由してAPIを叩く
// app/api/proxy/route.js
export async function GET() {
  // サーバー間の通信はブラウザのCORS制約を受けない
  const res = await fetch('https://api.example.com/users');
  const data = await res.json();
  return Response.json(data);
}

// クライアントコンポーネント側は自ドメインのAPI Routeを呼ぶだけでよい
fetch('/api/proxy')
  .then(res => res.json())
  .then(data => console.log(data));
Route Handlerはサーバー上で実行されるため、外部APIとの通信をここに集約すればフロントエンド側でCORS設定を気にする必要がなくなります。

Redirect is not allowed for a preflight requestなど別パターンのCORSエラーはどう対処する?

Redirect is not allowed for a preflight request と表示される場合

プリフライトのOPTIONSレスポンスは200番台のステータスコードで返す必要があり、リダイレクトは許可されません。エンドポイントの末尾スラッシュ有無でサーバーがリダイレクトを返している場合に発生しやすいパターンです。

// 末尾のスラッシュ有無でリダイレクトが発生するAPIエンドポイント
fetch('https://api.example.com/orders', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ item: 'book' }),
});
// サーバーが /orders を /orders/ へ301リダイレクトしていると
// プリフライトのOPTIONSリクエストがリダイレクトとして返され、ブロックされる

// 解決策: リダイレクトされない正しいパスに最初からリクエストする
fetch('https://api.example.com/orders/', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ item: 'book' }),
});

must not be the wildcard ‘*’ と表示される場合

Cookieなどの認証情報を送るcredentials: 'include'を指定したリクエストでは、サーバーのAccess-Control-Allow-Originにワイルドカード*を使うことができません。具体的なオリジンを1つ指定する必要があります。

// Cookieを送信するためcredentials: 'include' を指定している
fetch('https://api.example.com/mypage', {
  credentials: 'include',
});
// サーバーが Access-Control-Allow-Origin: * を返していると
// credentials込みのリクエストでは許可されずブロックされる

// サーバー側 (Express + cors) を修正
app.use(cors({
  origin: 'https://example.com', // ワイルドカードではなく具体的なオリジンを指定
  credentials: true,             // Access-Control-Allow-Credentials: true を付与
}));

CORSエラーはどうデバッグすればいい?

CORSエラーはJavaScriptのコード側から詳細な理由を取得できない仕様になっているため、まずブラウザの開発者ツールのNetworkタブでレスポンスヘッダーを直接確認するのが基本です。加えて、curlでブラウザを介さずリクエストを送ってみると、サーバー側の応答そのものを切り分けられます。

# ターミナルでcurlを使い、ブラウザを介さずレスポンスヘッダーを直接確認する (curl 8系)
curl -i -H "Origin: https://example.com" https://api.example.com/users

# レスポンスヘッダーに Access-Control-Allow-Origin が含まれているかを確認する
# 含まれていなければサーバー側の設定不足が原因と断定できる
ITKAGYO運営者デプロイ太郎のアイコン画像

Networkタブだけでなく、リクエストがそもそもプリフライトなのか本リクエストなのかを見分けるのもコツです。一つずつ確認していきましょう。

CORSエラーを未然に防ぐには?

CORSエラーは開発の終盤で発覚すると手戻りが大きくなりがちです。API設計の初期段階で許可オリジンの一覧を決め、サーバー側の設定を一箇所にまとめておくと、あとから追加・変更するときの漏れを防ぎやすくなります。

// バックエンドでオリジンを一元管理し、環境ごとに切り替える (Node.js 20 + Express 4)
const allowedOrigins = [
  'https://example.com',
  'http://localhost:5173', // 開発環境用
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
}));
許可するオリジンをコード内の1箇所にまとめておくと、新しいフロントエンドのドメインを追加するときの設定漏れを防ぎやすくなります。

no-corsモードを使えばCORSエラーは解決する?

fetchのオプションにmode: 'no-cors'を指定すると、コンソール上の赤いエラーは確かに消えます。しかしこれはCORSエラーの解決ではなく、レスポンスを不透明(opaque)な状態で受け取っているだけで、JavaScriptからレスポンスの中身やステータスコードを読み取ることはできません。

ログ送信のようにレスポンス内容を使わない一方向の通信であれば実用上問題になりませんが、APIのデータを画面に表示したい用途では使えません。エラーが消えたからといって安易にこのモードへ切り替えると、データが取得できていないことに気づかないまま本番運用してしまう危険があります。

ITKAGYO運営者デプロイ太郎のアイコン画像

原因がわかりにくいエラーですが、仕組みを理解すれば対処のパターンは限られています。落ち着いて一つずつ潰していきましょう。

よくある質問

Q
本番環境でだけCORSエラーが発生するのはなぜですか?
A

開発環境ではビルドツールのプロキシ機能によって同一オリジン扱いになっていたリクエストが、本番ビルドではフロントの配信ドメインとAPIのドメインが実際に分かれるため、CORS制約が初めて表面化するからです。デプロイ前に本番相当のドメイン構成で動作確認しておくことが重要になります。

Q
Next.jsでAPI Routesを経由すればCORSエラーは必ず解消されますか?
A

サーバー間通信になるためブラウザのCORS制約自体は受けなくなりますが、外部API側の認証やレート制限まで肩代わりできるわけではありません。Route Handler内でのエラーハンドリングは別途実装しておく必要があります。

Q
ESLintのようなツールでCORSエラーを事前に検出できますか?
A

CORSはサーバーとブラウザの間で実行時に発生する現象のため、静的解析だけでは検出できません。CI上でOriginヘッダーを付けたcurlリクエストを実行し、レスポンスヘッダーを検証するスモークテストを組み込んでおくと、事前に気づきやすくなります。

Q
CORSエラーが起きたときユーザーにはどう表示すればいいですか?
A

fetchのcatchブロックで通信エラーを検知し、通信環境をご確認くださいのような汎用的な文言に置き換えるのが基本です。CORSの技術的な詳細をそのままユーザー画面に出すことは避け、詳細はログ収集ツール側で確認できるようにしておきます。

Q
CORSエラーとJavaScript TypeError: Failed to fetchの違いは何ですか?
A

CORSエラーはサーバーがレスポンスを返しているのにブラウザが読み取りを拒否する現象であるのに対し、Failed to fetchはネットワーク断や証明書エラーなどでリクエスト自体が成立しなかった場合に発生します。Networkタブでレスポンスが返っているかどうかを確認すれば、どちらの現象かを切り分けられます。

この記事と一緒に知っておきたいエラー解決

関連エラー この記事との関連
JavaScript TypeError: Failed to fetch CORSエラーとよく混同される、リクエスト自体が成立しない別種のfetchエラーとの切り分けに役立つ。
JavaScript Unhandled Rejection (Promise error) CORSエラーでfetchのPromiseがcatchされないまま未処理の拒否として現れるケースを補足できる。
JavaScript SyntaxError: Unexpected token ‘<‘ in JSON CORS設定を直した後もサーバーがエラーページ(HTML)を返していると、fetchのjson()解析で発生しやすい後続エラー。
JavaScript TypeError: Cannot destructure property of null CORSエラーでレスポンスを取得できずnullのままプロパティを分割代入してしまった際に発生しやすい。
JavaScript TypeError: Cannot read properties of undefined CORSエラーでデータ取得に失敗した後、未定義の値を参照して二次的に発生しやすいエラー。

【出典】参考URL

https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS:CORSの仕組み、プリフライトリクエスト、Access-Control-Allow-Originの基本仕様
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors:ブラウザに表示される各種CORSエラーメッセージの一覧
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors/CORSMissingAllowOrigin:Access-Control-Allow-Originヘッダー欠落時のエラー文言と対処法
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Origin:Access-Control-Allow-Originヘッダーの仕様

コメント

デプロイ太郎のSNSを見てみる!!