T-CREATOR

【解決策】Next.js での CORS エラーの原因と対処方法まとめ

【解決策】Next.js での CORS エラーの原因と対処方法まとめ

Next.js での開発中に突然現れる「CORS エラー」に、多くの開発者が悩まされています。特に外部 API との連携を行う際に発生するこのエラーは、初心者にとって理解が困難で、解決に時間がかかることが多いのが現状です。

この記事では、Next.js 特有の CORS エラーの原因から具体的な解決策まで、実践的なアプローチでご紹介いたします。開発の効率を大幅に向上させる方法を、順を追って詳しく解説していきます。

背景

CORS(Cross-Origin Resource Sharing)とは

CORS は、異なるオリジン(ドメイン、プロトコル、ポート)間でのリソース共有を制御するセキュリティ機能です。ブラウザが実装しているこの仕組みにより、悪意のあるサイトからの不正なリクエストを防いでいます。

以下の図で、CORS の基本的な動作を確認しましょう。

mermaidsequenceDiagram
    participant Browser as ブラウザ
    participant Origin as オリジンサーバー<br/>(localhost:3000)
    participant API as 外部API<br/>(api.example.com)

    Browser->>Origin: 1. ページアクセス
    Origin->>Browser: 2. HTML/JS配信
    Browser->>API: 3. API リクエスト
    API-->>Browser: 4. CORS ヘッダーチェック
    alt CORS 設定OK
        API->>Browser: 5. レスポンス返却
    else CORS エラー
        API-->>Browser: 5. エラー: ブロック
    end

図で理解できる要点:

  • ブラウザが異なるオリジン間の通信を監視
  • API サーバーが適切な CORS ヘッダーを返す必要がある
  • 設定が不適切な場合、ブラウザがリクエストをブロック

ブラウザのセキュリティ機能としての役割

CORS は Same-Origin Policy(同一オリジンポリシー)を緩和するための仕組みです。同一オリジンポリシーは、セキュリティ上の理由から、異なるオリジンへのリクエストを原則として禁止しています。

項目説明
オリジンプロトコル + ドメイン + ポートhttps:​/​​/​example.com:443
同一オリジン3 つの要素がすべて同じhttps:​/​​/​example.com​/​api
異なるオリジンいずれかの要素が異なるhttp:​/​​/​api.example.com

Next.js でのデータフェッチの特殊性

Next.js では、サーバーサイドレンダリング(SSR)とクライアントサイドレンダリング(CSR)の両方でデータフェッチが行われます。それぞれで CORS の扱いが異なるため、注意が必要です。

javascript// サーバーサイド(getServerSideProps)では CORS エラーは発生しない
export async function getServerSideProps() {
  // サーバーから直接 API を呼び出すため CORS 制約なし
  const response = await fetch(
    'https://api.example.com/data'
  );
  const data = await response.json();

  return {
    props: { data },
  };
}
javascript// クライアントサイドでは CORS エラーが発生する可能性がある
useEffect(() => {
  // ブラウザから直接 API を呼び出すため CORS 制約あり
  fetch('https://api.example.com/data')
    .then((response) => response.json())
    .then((data) => setData(data))
    .catch((error) => console.error('CORS Error:', error));
}, []);

課題

よくある CORS エラーのパターン

Next.js 開発者が遭遇する代表的な CORS エラーパターンを整理してみましょう。

mermaidflowchart TD
    A[CORS エラー] --> B[Access-Control-Allow-Origin エラー]
    A --> C[プリフライトリクエストエラー]
    A --> D[認証情報エラー]

    B --> B1[オリジンが許可されていない]
    B --> B2[ワイルドカード設定の問題]

    C --> C1[OPTIONS リクエストが失敗]
    C --> C2[カスタムヘッダーが拒否]

    D --> D1[withCredentials 設定不備]
    D --> D2[Cookie 送信エラー]

エラーメッセージの読み方

CORS エラーには特徴的なエラーメッセージが表示されます。正確な原因を特定するために、メッセージの読み方を理解しておきましょう。

パターン 1: Origin エラー

csharpAccess to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

原因: API サーバーが Access-Control-Allow-Origin ヘッダーを返していない

パターン 2: プリフライトエラー

vbnetAccess to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass access
control check: No 'Access-Control-Allow-Methods' header is present on the requested resource.

原因: プリフライトリクエスト(OPTIONS)への適切な応答がない

パターン 3: 認証情報エラー

csharpAccess to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials'
header in the response is '' which must be 'true' when the request's credentials
mode is 'include'.

原因: 認証情報を含むリクエストで、サーバーが Access-Control-Allow-Credentials: true を返していない

開発環境と本番環境での違い

環境特徴よくある問題
開発環境localhost127.0.0.1 を使用ポート番号の違いによる CORS エラー
本番環境実際のドメインを使用環境変数の設定ミス、プロキシ設定の違い
プレビュー環境一時的なドメインを使用動的ドメインでの CORS 設定困難

開発環境では動作していたものが、本番環境でエラーになるケースが頻繁に発生します。これは環境ごとの URL の違いや、API サーバーの CORS 設定が開発用のオリジンのみを許可しているためです。

解決策

Next.js API Routes を使った解決法

最も確実で推奨される解決策は、Next.js の API Routes を活用してプロキシサーバーを構築することです。この方法により、フロントエンドとバックエンド API の間に中継層を設けて CORS 問題を回避できます。

基本的なプロキシ実装

まず、API Routes を使ったシンプルなプロキシを実装してみましょう。

javascript// pages/api/proxy.js
export default async function handler(req, res) {
  // CORS ヘッダーを設定
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, DELETE, OPTIONS'
  );
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization'
  );

  // プリフライトリクエストの処理
  if (req.method === 'OPTIONS') {
    res.status(200).end();
    return;
  }

  try {
    // 外部 API への転送
    const response = await fetch(
      'https://api.example.com/data',
      {
        method: req.method,
        headers: {
          'Content-Type': 'application/json',
          // 必要に応じて認証ヘッダーを追加
          Authorization: req.headers.authorization || '',
        },
        body:
          req.method !== 'GET'
            ? JSON.stringify(req.body)
            : undefined,
      }
    );

    const data = await response.json();
    res.status(response.status).json(data);
  } catch (error) {
    res
      .status(500)
      .json({ error: 'プロキシでエラーが発生しました' });
  }
}

App Router での実装

Next.js 13 以降の App Router を使用している場合は、Route Handlers を使用します。

javascript// app/api/proxy/route.js
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const endpoint = searchParams.get('endpoint');

  try {
    const response = await fetch(
      `https://api.example.com/${endpoint}`,
      {
        headers: {
          'Content-Type': 'application/json',
        },
      }
    );

    const data = await response.json();

    return Response.json(data, {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods':
          'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers':
          'Content-Type, Authorization',
      },
    });
  } catch (error) {
    return Response.json(
      { error: 'API エラーが発生しました' },
      { status: 500 }
    );
  }
}

export async function POST(request) {
  const body = await request.json();

  try {
    const response = await fetch(
      'https://api.example.com/data',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
      }
    );

    const data = await response.json();

    return Response.json(data, {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods':
          'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers':
          'Content-Type, Authorization',
      },
    });
  } catch (error) {
    return Response.json(
      { error: 'API エラーが発生しました' },
      { status: 500 }
    );
  }
}

クライアントサイドでの使用方法

作成したプロキシ API を使用してデータを取得します。

javascript// components/DataFetcher.js
import { useState, useEffect } from 'react';

export default function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 直接外部 API を呼ぶ代わりに、自分の API Routes を経由
    fetch('/api/proxy?endpoint=users')
      .then((response) => {
        if (!response.ok) {
          throw new Error(
            `HTTP error! status: ${response.status}`
          );
        }
        return response.json();
      })
      .then((result) => {
        setData(result);
        setLoading(false);
      })
      .catch((err) => {
        setError(err.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error}</div>;

  return (
    <div>
      <h2>取得データ</h2>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

middleware.js での設定方法

Next.js の Middleware を使用すれば、すべてのリクエストに対して包括的な CORS 設定を適用できます。

基本的な Middleware 実装

javascript// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  // CORS ヘッダーを含むレスポンスを作成
  const response = NextResponse.next();

  // 基本的な CORS ヘッダーを設定
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, DELETE, OPTIONS'
  );
  response.headers.set(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization, X-Requested-With'
  );
  response.headers.set('Access-Control-Max-Age', '86400');

  // プリフライトリクエストの処理
  if (request.method === 'OPTIONS') {
    return new Response(null, {
      status: 200,
      headers: response.headers,
    });
  }

  return response;
}

// API ルートにのみ適用
export const config = {
  matcher: '/api/:path*',
};

環境別の設定

開発環境と本番環境で異なる CORS 設定を適用する場合の実装例です。

javascript// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  const response = NextResponse.next();

  // 環境に応じてオリジンを設定
  const allowedOrigins =
    process.env.NODE_ENV === 'development'
      ? ['http://localhost:3000', 'http://127.0.0.1:3000']
      : [
          'https://yourdomain.com',
          'https://www.yourdomain.com',
        ];

  const origin = request.headers.get('origin');

  if (origin && allowedOrigins.includes(origin)) {
    response.headers.set(
      'Access-Control-Allow-Origin',
      origin
    );
  }

  response.headers.set(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, DELETE, OPTIONS'
  );
  response.headers.set(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization, X-Requested-With'
  );
  response.headers.set(
    'Access-Control-Allow-Credentials',
    'true'
  );

  if (request.method === 'OPTIONS') {
    return new Response(null, {
      status: 200,
      headers: response.headers,
    });
  }

  return response;
}

export const config = {
  matcher: '/api/:path*',
};

プロキシ設定による回避策

next.config.js でのプロキシ設定

開発環境での CORS エラーを回避するために、Next.js の設定でプロキシを構成できます。

javascript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/external/:path*',
        destination: 'https://api.example.com/:path*',
      },
    ];
  },

  // CORS ヘッダーを追加
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          {
            key: 'Access-Control-Allow-Origin',
            value: '*',
          },
          {
            key: 'Access-Control-Allow-Methods',
            value: 'GET, POST, PUT, DELETE, OPTIONS',
          },
          {
            key: 'Access-Control-Allow-Headers',
            value: 'Content-Type, Authorization',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

環境変数を使った動的設定

javascript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async rewrites() {
    if (process.env.NODE_ENV === 'development') {
      return [
        {
          source: '/api/proxy/:path*',
          destination: `${process.env.API_BASE_URL}/:path*`,
        },
      ];
    }
    return [];
  },

  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          {
            key: 'Access-Control-Allow-Origin',
            value: process.env.ALLOWED_ORIGINS || '*',
          },
          {
            key: 'Access-Control-Allow-Methods',
            value: 'GET, POST, PUT, DELETE, OPTIONS',
          },
          {
            key: 'Access-Control-Allow-Headers',
            value: 'Content-Type, Authorization, X-API-Key',
          },
          {
            key: 'Access-Control-Allow-Credentials',
            value: 'true',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

環境変数の設定例:

bash# .env.local(開発環境)
API_BASE_URL=https://api.example.com
ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000

# .env.production(本番環境)
API_BASE_URL=https://api.production.com
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com

具体例

外部 API との連携実装例

実際の外部 API(例:GitHub API)との連携を通じて、CORS 対応の実装方法を詳しく見ていきましょう。

GitHub API プロキシの実装

javascript// pages/api/github/[...params].js
export default async function handler(req, res) {
  // GitHub API のベース URL
  const GITHUB_API_BASE = 'https://api.github.com';

  // CORS ヘッダーを設定
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, DELETE, OPTIONS'
  );
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization'
  );

  // プリフライトリクエストの処理
  if (req.method === 'OPTIONS') {
    res.status(200).end();
    return;
  }

  try {
    // URL パラメータから GitHub API のパスを構築
    const { params } = req.query;
    const apiPath = Array.isArray(params)
      ? params.join('/')
      : params;
    const githubUrl = `${GITHUB_API_BASE}/${apiPath}`;

    // GitHub API への認証
    const headers = {
      Accept: 'application/vnd.github.v3+json',
      'User-Agent': 'Next.js-CORS-Proxy',
    };

    // GitHub トークンがある場合は認証ヘッダーを追加
    if (process.env.GITHUB_TOKEN) {
      headers[
        'Authorization'
      ] = `token ${process.env.GITHUB_TOKEN}`;
    }

    // GitHub API を呼び出し
    const response = await fetch(githubUrl, {
      method: req.method,
      headers,
      body:
        req.method !== 'GET'
          ? JSON.stringify(req.body)
          : undefined,
    });

    if (!response.ok) {
      throw new Error(
        `GitHub API Error: ${response.status} ${response.statusText}`
      );
    }

    const data = await response.json();
    res.status(200).json(data);
  } catch (error) {
    console.error('GitHub API プロキシエラー:', error);
    res.status(500).json({
      error: 'GitHub API の呼び出しに失敗しました',
      message: error.message,
    });
  }
}

クライアントサイドでの使用

javascript// components/GitHubRepos.js
import { useState, useEffect } from 'react';

export default function GitHubRepos({ username }) {
  const [repos, setRepos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!username) return;

    // プロキシ経由で GitHub API を呼び出し
    fetch(`/api/github/users/${username}/repos`)
      .then((response) => {
        if (!response.ok) {
          throw new Error(
            `HTTP error! status: ${response.status}`
          );
        }
        return response.json();
      })
      .then((data) => {
        setRepos(data);
        setLoading(false);
      })
      .catch((err) => {
        setError(err.message);
        setLoading(false);
      });
  }, [username]);

  if (loading) return <div>リポジトリを読み込み中...</div>;
  if (error) return <div>エラー: {error}</div>;

  return (
    <div>
      <h2>{username} のリポジトリ</h2>
      <ul>
        {repos.map((repo) => (
          <li key={repo.id}>
            <a
              href={repo.html_url}
              target='_blank'
              rel='noopener noreferrer'
            >
              {repo.name}
            </a>
            <p>{repo.description}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

認証付き API の CORS 対応

認証トークンを必要とする API との連携では、追加の CORS 設定が必要になります。

JWT トークン認証の実装

javascript// pages/api/auth/[...params].js
export default async function handler(req, res) {
  // 認証が必要な API では、より厳格な CORS 設定を適用
  const allowedOrigins = [
    'http://localhost:3000',
    'https://yourdomain.com',
  ];

  const origin = req.headers.origin;

  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }

  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, DELETE, OPTIONS'
  );
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization'
  );

  if (req.method === 'OPTIONS') {
    res.status(200).end();
    return;
  }

  try {
    // リクエストヘッダーから認証トークンを取得
    const authHeader = req.headers.authorization;
    if (!authHeader) {
      return res
        .status(401)
        .json({ error: '認証トークンが必要です' });
    }

    // 外部 API のベース URL
    const API_BASE_URL = process.env.API_BASE_URL;
    const { params } = req.query;
    const apiPath = Array.isArray(params)
      ? params.join('/')
      : params;

    // 外部 API へのリクエスト
    const response = await fetch(
      `${API_BASE_URL}/${apiPath}`,
      {
        method: req.method,
        headers: {
          'Content-Type': 'application/json',
          Authorization: authHeader,
        },
        body:
          req.method !== 'GET'
            ? JSON.stringify(req.body)
            : undefined,
      }
    );

    if (!response.ok) {
      throw new Error(
        `API Error: ${response.status} ${response.statusText}`
      );
    }

    const data = await response.json();
    res.status(200).json(data);
  } catch (error) {
    console.error('認証付き API エラー:', error);
    res.status(500).json({
      error: 'API の呼び出しに失敗しました',
      message: error.message,
    });
  }
}

認証コンテキストとの連携

javascript// context/AuthContext.js
import {
  createContext,
  useContext,
  useState,
  useEffect,
} from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [token, setToken] = useState(null);
  const [user, setUser] = useState(null);

  // ローカルストレージからトークンを復元
  useEffect(() => {
    const savedToken = localStorage.getItem('authToken');
    if (savedToken) {
      setToken(savedToken);
      // トークンの有効性を確認
      verifyToken(savedToken);
    }
  }, []);

  const verifyToken = async (authToken) => {
    try {
      const response = await fetch('/api/auth/verify', {
        headers: {
          Authorization: `Bearer ${authToken}`,
        },
      });

      if (response.ok) {
        const userData = await response.json();
        setUser(userData);
      } else {
        // トークンが無効な場合はクリア
        logout();
      }
    } catch (error) {
      console.error('トークン検証エラー:', error);
      logout();
    }
  };

  const login = async (credentials) => {
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(credentials),
      });

      if (response.ok) {
        const { token: newToken, user: userData } =
          await response.json();
        setToken(newToken);
        setUser(userData);
        localStorage.setItem('authToken', newToken);
        return true;
      } else {
        throw new Error('ログインに失敗しました');
      }
    } catch (error) {
      console.error('ログインエラー:', error);
      return false;
    }
  };

  const logout = () => {
    setToken(null);
    setUser(null);
    localStorage.removeItem('authToken');
  };

  return (
    <AuthContext.Provider
      value={{ token, user, login, logout }}
    >
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

複数ドメインでの設定例

複数のフロントエンドドメインから同一の API にアクセスする場合の設定方法を見ていきます。

動的オリジン許可の実装

javascript// pages/api/multi-origin/[...params].js
export default async function handler(req, res) {
  // 許可するオリジンのリスト(環境変数から取得)
  const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(
    ','
  ) || [
    'http://localhost:3000',
    'http://localhost:3001',
    'https://app.yourdomain.com',
    'https://admin.yourdomain.com',
    'https://mobile.yourdomain.com',
  ];

  const origin = req.headers.origin;

  // オリジンチェック
  if (origin && allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }

  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, DELETE, OPTIONS'
  );
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization, X-API-Key'
  );

  // プリフライトリクエストの処理
  if (req.method === 'OPTIONS') {
    res.status(200).end();
    return;
  }

  try {
    // API キーによる認証
    const apiKey = req.headers['x-api-key'];
    if (!apiKey || apiKey !== process.env.API_KEY) {
      return res
        .status(401)
        .json({ error: 'API キーが無効です' });
    }

    // 外部 API への転送処理
    const { params } = req.query;
    const apiPath = Array.isArray(params)
      ? params.join('/')
      : params;

    const response = await fetch(
      `${process.env.EXTERNAL_API_URL}/${apiPath}`,
      {
        method: req.method,
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${process.env.EXTERNAL_API_TOKEN}`,
        },
        body:
          req.method !== 'GET'
            ? JSON.stringify(req.body)
            : undefined,
      }
    );

    const data = await response.json();
    res.status(response.status).json(data);
  } catch (error) {
    console.error('マルチオリジン API エラー:', error);
    res.status(500).json({
      error: 'API の処理中にエラーが発生しました',
      message: error.message,
    });
  }
}

環境変数設定の例

bash# .env.local(開発環境)
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000
EXTERNAL_API_URL=https://api.development.com
EXTERNAL_API_TOKEN=dev_token_here
API_KEY=dev_api_key_here

# .env.production(本番環境)
ALLOWED_ORIGINS=https://app.yourdomain.com,https://admin.yourdomain.com,https://mobile.yourdomain.com
EXTERNAL_API_URL=https://api.production.com
EXTERNAL_API_TOKEN=prod_token_here
API_KEY=prod_api_key_here

以下の図で、複数ドメインでの CORS 設定の流れを確認しましょう。

mermaidflowchart TD
    A[複数のフロントエンド] --> B{オリジンチェック}
    A1[app.yourdomain.com] --> B
    A2[admin.yourdomain.com] --> B
    A3[mobile.yourdomain.com] --> B

    B -->|許可リストに存在| C[CORS ヘッダー設定]
    B -->|許可リストに不存在| D[アクセス拒否]

    C --> E[API プロキシ処理]
    E --> F[外部 API 呼び出し]
    F --> G[レスポンス返却]

    D --> H[エラーレスポンス]

図で理解できる要点:

  • 複数のオリジンからのアクセスを動的に制御
  • 許可リストベースでのセキュリティ確保
  • 環境に応じた柔軟な設定変更が可能

まとめ

最適な解決策の選択指針

Next.js での CORS エラー解決には複数のアプローチがありますが、状況に応じて最適な方法を選択することが重要です。

状況推奨解決策理由
開発環境のみでエラー発生next.config.js のプロキシ設定設定が簡単で開発効率が良い
本番環境でも対応が必要API Routes によるプロキシセキュリティとパフォーマンスを両立
複数の API との連携Middleware による包括設定統一的な CORS 制御が可能
認証が必要な APIAPI Routes + 認証処理セキュリティを最優先で実装

パフォーマンスとセキュリティの両立

CORS 対応を実装する際は、以下の点を考慮してパフォーマンスとセキュリティのバランスを取ることが大切です。

セキュリティ面での考慮事項

javascript// セキュアな CORS 設定の例
export default async function handler(req, res) {
  // 具体的なオリジンを指定(ワイルドカードは避ける)
  const allowedOrigins = [
    'https://yourdomain.com',
    'https://www.yourdomain.com',
  ];

  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }

  // 認証情報を含む場合は特に注意
  res.setHeader('Access-Control-Allow-Credentials', 'true');

  // 必要最小限のメソッドのみ許可
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST'
  );

  // 必要なヘッダーのみ許可
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Content-Type, Authorization'
  );

  // プリフライトリクエストのキャッシュ時間を制限
  res.setHeader('Access-Control-Max-Age', '3600'); // 1時間
}

パフォーマンス最適化のポイント

  1. プリフライトリクエストの最適化: Access-Control-Max-Age を適切に設定してキャッシュを活用
  2. API Routes の効率化: 不要な処理を避け、エラーハンドリングを適切に実装
  3. 環境別設定: 開発環境では緩い設定、本番環境では厳格な設定を適用

Next.js での CORS エラーは適切なアプローチで確実に解決できます。まずは API Routes を使ったプロキシから始めて、プロジェクトの要件に応じて設定を調整していくことをお勧めいたします。セキュリティを保ちながら、開発効率を向上させる実装を心がけましょう。

関連リンク