SSRとSPAでCORS設計が変わる理由を解説

SSRとSPAでCORS設計が変わる理由をブラウザセキュリティモデルから解説

CORS(Cross-Origin Resource Sharing)は、SPAを作ると突然意識するようになる仕組みです。一方でSSR中心の構成では、ほとんど意識せずに済む場合があります。

これは設定の難易度の違いではありません。通信の主体がブラウザかサーバかという、セキュリティモデルの違いによるものです。

CORSはサーバの機能ではなく、ブラウザの防御機構です。ここを理解すると「なぜSPAだけCORSエラーが出るのか」がはっきりします。

同一オリジンポリシーとは何か

ブラウザは基本的に、異なるオリジン間の通信を制限します。これを同一オリジンポリシーと呼びます。

オリジンは次の3つの組み合わせです。

  • スキーム(http / https)
  • ホスト名
  • ポート

たとえば次は別オリジンです。

JavaScriptから別オリジンへアクセスしようとすると、ブラウザがブロックします。これを緩和する仕組みがCORSです。

SPAでCORSが必要になる理由

SPAでは、ブラウザが直接APIを呼びます。

fetch("https://api.example.com/users");

このときブラウザは「別サイトへのアクセス」と判断し、サーバに許可を要求します。これがプリフライトリクエストです。

OPTIONS /users
Origin: https://example.com

サーバが許可すると、実際の通信が行われます。

Access-Control-Allow-Origin: https://example.com

このヘッダがないと、レスポンスは届いていてもJavaScriptから参照できません。ここがCORSエラーの正体です。

SSRではなぜCORSを意識しないのか

SSRではブラウザがAPIを呼びません。サーバが呼びます。

  • ブラウザ → SSRサーバ
  • SSRサーバ → APIサーバ

サーバ間通信には同一オリジンポリシーが適用されません。ブラウザのセキュリティ機構だからです。

つまりCORSは発生しません。単なるHTTP通信です。

ここが大きな違いです。SPAはブラウザが通信主体、SSRはサーバが通信主体です。

CORSと認証の関係

さらに混乱を招くのが認証です。Cookieを使う場合、次の設定が必要になります。

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://example.com

ワイルドカード(*)は使えません。ブラウザが認証情報付き通信を拒否するためです。

この制約により、SPAでは環境ごとにCORS設定が必要になります。

  • localhost
  • staging
  • production

設定漏れがログイン失敗として現れます。

プリフライトが性能に影響する

CORSにはOPTIONSリクエストが発生します。これがプリフライトです。

特に次の場合に起きます。

  • カスタムヘッダ
  • JSON POST
  • 認証ヘッダ

つまり、一般的なSPAのAPI通信はほぼ毎回プリフライトが発生します。通信回数が2倍になり、レイテンシが増加します。

OPTIONS /api
→ 200
POST /api
→ 200

モバイル回線では体感速度に影響が出ます。

SSRとSPAの設計判断

この違いにより、API公開範囲の考え方が変わります。

SPA:

  • 公開APIとして扱う必要
  • 外部アクセス前提
  • CORS設計必須

SSR:

  • 内部通信として扱える
  • 非公開APIにできる
  • CORS不要

セキュリティの境界が変わる点が重要です。

よくある失敗

SPA移行時に起きやすい問題があります。

  • 本番だけCORSエラー
  • Cookieが送信されない
  • OPTIONSが405になる

原因はAPIサーバがブラウザアクセスを想定していないことです。従来はサーバ間通信しかなかったため、CORSヘッダが実装されていません。

まとめ

CORSはAPIの機能ではなく、ブラウザのセキュリティ制御です。通信主体がブラウザになるSPAでは必須になり、サーバ主体のSSRではほぼ不要になります。

重要なのは「どこが信頼境界か」です。SPAではブラウザが境界、SSRではサーバが境界になります。この違いを理解して設計すると、CORSエラーや認証トラブルを大幅に減らせます。