CORSエラーとは?
Reactフロントエンドを構築してExpress.jsバックエンドと通信する場合、ほぼ確実にブラウザコンソールで以下のエラーを見たことがあるでしょう:
Access to fetch at 'http://localhost:3001/api/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
CORSはCross-Origin Resource Sharing(オリジン間リソース共有)の略です。あるオリジン(ドメイン + ポート + プロトコル)から別のオリジンへのHTTPリクエストを制限するブラウザのセキュリティメカニズムです。このポリシーを適用するのはブラウザであり、サーバーではありません。リクエストがオリジンを越える場合、ブラウザはまずサーバーが許可しているかどうかを特定のHTTPレスポンスヘッダー、主にAccess-Control-Allow-Originで確認します。
これらのヘッダーが欠落または不正確な場合、ブラウザはレスポンスをブロックしCORSエラーをスローします — サーバーが200 OKを返していても同様です。これはExpressのバグではなく、ブラウザが設計通りに動作しているのです。
なぜReact + ExpressでCORSが発生するのか?
典型的なReact + Expressの開発環境では、2つのサーバーを別々に実行します:
- React開発サーバー:
http://localhost:3000(Create React App)またはhttp://localhost:5173(Vite) - Express APIサーバー:
http://localhost:3001(または別のポート)
ポートが異なるため、ブラウザはこれらを別のオリジンとして扱います。ReactアプリからExpress APIへのfetchやaxios呼び出しはすべてクロスオリジンリクエストとなり、Expressサーバーが明示的に許可しない限りブラウザがブロックします。
本番環境では、同じドメインにデプロイすれば(例:Next.jsで両方を配信、またはNginxリバースプロキシで/apiをExpressにルーティング)問題が解消されることが多いですが、開発中に2つのサーバーを別々に実行するとCORSエラーはほぼ確実に発生します。
クイックフィックス:corsミドルウェアをインストール
ExpressでCORSを修正する最速の方法は、公式cors npmパッケージを使用することです。インストール:
npm install cors
そしてExpressアプリのルート定義の前に追加します:
// server.js (or app.js)
const express = require('express');
const cors = require('cors');
const app = express();
// Allow all origins — only use this in development
app.use(cors());
app.use(express.json());
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello from Express!' });
});
app.listen(3001, () => {
console.log('Server running on http://localhost:3001');
});
引数なしでapp.use(cors())を呼び出すと、すべてのレスポンスにAccess-Control-Allow-Origin: *が追加され、任意のオリジンからのリクエストが許可されます。これはローカル開発では問題ありませんが、認証データを扱うAPIでは本番環境で使用してはいけません。
本番環境の設定
本番環境では、許可するオリジンを正確に指定し、クレデンシャル、メソッド、ヘッダーを明示的に設定します:
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://www.yourdomain.com',
'https://yourdomain.com',
'http://localhost:3000', // Keep for local dev
'http://localhost:5173', // Vite dev server
];
// Allow requests with no origin (e.g., curl, Postman, server-to-server)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`CORS blocked for origin: ${origin}`));
}
},
credentials: true, // Allow cookies and Authorization headers
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['Content-Range', 'X-Content-Range'],
maxAge: 86400, // Cache preflight response for 24 hours
};
app.use(cors(corsOptions));
// Explicitly handle preflight requests for all routes
app.options('*', cors(corsOptions));
この設定のポイント:
credentials: true— ReactアプリがCookieやAuthorizationヘッダーを送信する場合に必要。これを設定するとoriginに*は使用できず、特定のオリジンを指定する必要があります。app.options('*', cors())— ブラウザは非シンプルリクエスト(PUT、PATCH、DELETE、カスタムヘッダー付きリクエスト)の前にHTTP OPTIONSプリフライトリクエストを送信します。Expressがこれに応答しないと、実際のリクエストがブロックされます。maxAge— プリフライトレスポンスのキャッシュ期間をブラウザに指示し、不要なOPTIONSリクエストを削減します。
開発環境でのReactプロキシ代替案
Create React Appを使用している場合、package.jsonにproxyフィールドを追加することで、開発中のCORSを完全に回避できます。これによりwebpack開発サーバーがAPIリクエストをExpressバックエンドに転送します:
// package.json (React project root)
{
"name": "my-react-app",
"version": "1.0.0",
"proxy": "http://localhost:3001",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
}
これにより、Reactコードでfetch('/api/data')(ホスト名なし)を呼び出すと、開発サーバーがリクエストをhttp://localhost:3001/api/dataにプロキシします — 同一オリジンなのでCORSは発生しません。このアプローチは開発中のみ機能し、本番環境ではデプロイメントでルーティングを処理する必要があります。
Viteプロキシ設定
Vite(Create React Appの代替)を使用している場合、vite.config.jsでプロキシを設定します:
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: {
// All requests starting with /api are forwarded to Express
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
// Optional: rewrite path if your Express routes don't start with /api
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
});
この設定により、Reactコンポーネントでfetch('/api/users')を呼び出すとhttp://localhost:3001/api/usersにプロキシされます。開発中はCORSヘッダーが不要で、Expressサーバーをクリーンに保てます。
よくある間違い
1. ワイルドカードオリジンとクレデンシャルの併用
この組み合わせは常に失敗します。ブラウザはAccess-Control-Allow-Origin: *とAccess-Control-Allow-Credentials: trueが同時に含まれるCORSレスポンスを拒否します:
// This will NOT work — browser rejects wildcard + credentials
app.use(cors({
origin: '*', // Wildcard
credentials: true, // Cannot combine these
}));
修正方法:'*'を特定のオリジン文字列またはオリジンを検証する関数に置き換えてください(上記の本番環境設定を参照)。
2. プリフライト用のOPTIONSハンドラーの欠落
ExpressアプリがOPTIONSリクエストを処理しない場合、PUT、PATCH、DELETE、カスタムヘッダーリクエストのプリフライトチェックが404を受け取り、実際のリクエストは送信されません。ルート定義の前に必ずapp.options('*', cors(corsOptions))を追加してください。
3. CORSヘッダーの手動設定とcorsパッケージの併用
Access-Control-Allow-Originヘッダーを二重に設定すると、ブラウザが重複値を検出しリクエストをブロックします。corsミドルウェアか手動ヘッダーのどちらか一方を選択し、両方は使用しないでください。
4. CORSミドルウェアをルートの後に追加
Expressのミドルウェアは登録された順序で実行されます。app.use(cors())をルートハンドラーの後に追加すると、ルートが先にマッチしCORSヘッダーが追加されません。CORSミドルウェアは常にミドルウェアスタックの先頭に配置してください。
CORSエラーのデバッグ
CORSエラーが発生した場合、ブラウザコンソールのメッセージでどのヘッダーが欠落またはミスマッチしているかがわかります。DevToolsのNetworkタブでExpressサーバーが返す実際のレスポンスヘッダーを確認し、期待される値と比較してください。
確認すべき一般的な項目:
- レスポンスに
Access-Control-Allow-Originが存在するか? - ヘッダーのオリジンがReactアプリのオリジン(プロトコルとポートを含む)と正確に一致するか?
- クレデンシャル付きリクエストの場合:
Access-Control-Allow-Credentials: trueが存在するか? - PUT/DELETEリクエストの場合:OPTIONSプリフライトが
Access-Control-Allow-Methods付きの200を返しているか? - カスタムヘッダー(
Authorizationなど)を送信していますか?Access-Control-Allow-Headersに含まれている必要があります。
CORSエラーを瞬時にデバッグ
CORSエラーメッセージをペーストするだけで、Express、Nginx、Apacheなどの正確な修正コードを取得 — 推測は不要です。
CORSエラーデバッガーを開く →関連開発者ツール
- CORSエラーデバッガー — エラーをペーストしてExpress、Nginx、Apacheなどの修正コードを取得
- JWTデコーダー — AuthorizationヘッダーとJWTペイロードを検証
- HTTPステータスコード — 404、403、401、500など全てのステータスコードを理解
- ENV インスペクター — Expressアプリの環境変数を検証
- 全ての無料開発者ツールを見る