WEBサービスを作っていると、外部のAPIからデータを取得する場面はよくある。
JavaScriptからデータを取得する基本的なコードはこうだ。
const res = await fetch("https://example.com/api/data.json");
const data = await res.json();
シンプルに見えるが、オリジンをまたいで fetch する場合には「CORS(Cross-Origin Resource Sharing)」という壁を意識する必要がある。
オリジン?
弁当の話ではない。🍱
オリジンとは スキーム + ドメイン + ポート の組み合わせのことだ。
以下のように、少しでも違えば別オリジンと見なされる。
| 比較 | 同一オリジン? |
|---|---|
blog.example.com ↔ blog.example.com | ✅ 同じ |
https:// ↔ http:// | ❌ 別(スキーム違い) |
example.com ↔ example2.com | ❌ 別(ドメイン違い) |
blog.example.com ↔ example.com | ❌ 別(サブドメイン違い) |
example.com ↔ example.com:8080 | ❌ 別(ポート違い) |
サブドメインが違うだけでも別オリジン扱いになる点は、意外と見落としがちなので気をつけたい。
CORS とは何か
ブラウザには「同一オリジンポリシー(Same-Origin Policy)」というセキュリティルールがある。 これは異なるオリジンへのリクエストを制限する仕組みで、悪意あるサイトが別サイトのAPIを勝手に呼び出すのを防ぐ目的がある。👿
CORS はこの制限を「許可された範囲で緩和する」仕組みだ。
APIサーバーがレスポンスに Access-Control-Allow-Origin ヘッダーを付けることで、ブラウザに「このオリジンからのアクセスは許可している」と伝える。
Access-Control-Allow-Origin: https://example.com
許可するのはサーバー側である
CORS でよく混乱するのが「どちらが許可するのか」という点だ。
これはAPIを提供するサーバー側であり、クライアント(ブラウザ)でどうこうするものではない。
# CORS エラーが発生する構図
ブラウザ(example.com)
└─fetch("https://api.other.com/data")
↑ ブラウザが Origin ヘッダーを付けてリクエスト
↑ サーバーの返答に Access-Control-Allow-Origin がなければブロック
逆に言えば、api.other.com 側がヘッダーを返すように設定すれば解決する。
クライアント側でできることは何もない。
一方でサーバーサイドfetchはCORS関係ない
意外に認識が甘いかも、な点としてCORS はあくまでブラウザのセキュリティ機構だ。
故にNode.js などのサーバーサイドコードから fetch する場合、ブラウザを介さないためCORSチェックは発生しない。
# サーバーサイド fetch(CORS 不要)
ブラウザ
└─ site.example.com のサーバーにリクエスト
└─ サーバー側コードが fetch する
└─ api.other.com/data.json
↑ サーバー同士の通信 → ブラウザ不在 → CORS チェックなし
各フレームワークで具体的に見てみる。
Node.js(素のサーバーサイドコード)
// ブラウザを介さないので CORS 不要
const res = await fetch("https://api.other.com/data.json");
const data = await res.json();
Next.js(App Router の Server Component)
// app/page.tsx はサーバーで実行されるため CORS 不要
export default async function Page() {
const res = await fetch("https://api.other.com/data.json");
const data = await res.json();
// ...
}
Pages Router の場合は getServerSideProps を使う。
// pages/index.tsx(Pages Router)
export async function getServerSideProps() {
const res = await fetch("https://api.other.com/data.json");
const data = await res.json();
return { props: { data } };
}
Nuxt.js(Vue.js の SSR フレームワーク)
// server/api/ 配下や useAsyncData のサーバー実行時は CORS 不要
const { data } = await useAsyncData(() =>
$fetch("https://api.other.com/data.json")
);
※ただし同じ useAsyncData でもクライアントサイドで再実行される場合は CORS が必要になるため、どこで動くコードかを意識することが重要。
いずれも「サーバーで実行されるコード」であれば CORS を気にせず fetch できる。
ついでに、そもそもサーバサイドのfetchになるサーバサイド言語であるPHPとGoがどんな感じになるかもサンプルを載せておく。
PHP
<?php
$response = file_get_contents("https://api.other.com/data.json");
$data = json_decode($response, true);
より実践的には curl を使うことも多い。
<?php
$ch = curl_init("https://api.other.com/data.json");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
Go
package main
import (
"encoding/json"
"net/http"
)
func main() {
resp, err := http.Get("https://api.other.com/data.json")
if err != nil {
// エラー処理
}
defer resp.Body.Close()
var data interface{}
json.NewDecoder(resp.Body).Decode(&data)
}
いずれもサーバー上で実行されるコードなので、CORS の設定は一切不要だ。
まとめ:fetch の種類と CORS の関係
| fetch の種類 | CORS が必要か |
|---|---|
| ブラウザ(クライアントサイド)から別オリジンへ | ✅ 必要 |
| SSR・Node.js(サーバーサイド)から別オリジンへ | ❌ 不要 |
| 同一オリジンへ | ❌ 不要 |
静的サイト(SSG)同士で連携する場合は、ブラウザから直接 fetch することになるので CORS 対応が必須になる。 一方、fetch する側(情報が欲しい側)が SSR であれば、サーバーサイドで fetch できるため CORS を気にせず済む設計が取れる。
ちなみにCORSの読み方は「コルス」や「クロス」と発音される。🦆