Next.js と TypeScript で、CORS 問題を解決する

TwitterFacebookHatena

TL;DR

このページでは、Next.js と TypeScript を使った開発における CORS (Cross-Origin Resource Sharing) の問題とその解決方法について解説します。Web 開発において CORS は避けて通れない問題です。

開発環境 バージョン
Next.js 13.4.3
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

エラー内容

開発ツールで見ると、次のようなエラーが出ることがあります。一体このエラーはどういう意味なのでしょうか。

「「A cross-origin resource sharing (CORS) request was blocked because of invalid or missing response headers of the request or the associated preflight request . To fix this issue, ensure the response to the CORS request and/or the associated preflight request are not missing headers and use valid header values. Note that if an opaque response is sufficient, the request's mode can be set to no-cors to fetch the resource with CORS disabled; that way CORS headers are not required but the response content is inaccessible (opaque).」」

これはどういうことかというと、あるウェブページが、別のウェブページからデータを取得しようとした時に、ブロックされてしまったようです。この問題は、ブラウザのセキュリティ機能の一つである「CORS(クロスオリジンリソース共有)」に関連しています。

CORS とは、異なるウェブページ間でデータを共有するためのルールです。ウェブブラウザは、セキュリティ上の理由から、CORS のルールに違反するリクエストをブロックしてしまいます。

このエラーメッセージでは、リクエストや事前確認リクエストのレスポンスヘッダーに、正しくない値が設定されているか、ヘッダーが欠落しているためにブロックされたと言っています。正しいヘッダーを使用してレスポンスを返すことで、この問題を解決できます。

もしも、ブラウザへのレスポンスが不要であれば、リクエストのモードを「no-cors」に設定することで、CORS が無効になります。ただし、この場合はレスポンスの内容にはアクセスできません。

要するに、このエラーメッセージは、「ウェブページが別のウェブページからデータを取得しようとしているけれど、CORS のルールに違反しているためにブロックされた」ということを伝えています。正しいヘッダーを設定するか、必要であればリクエストのモードを変更して解決する必要があります。

CORS 問題の説明と対策

まずはじめに、CORS の問題とその原因を理解しましょう。CORS(Cross-Origin Resource Sharing)とは、異なるオリジン間でのリソース共有を制御するための仕組みです。サーバーが CORS ポリシーを適切に設定していない場合、ブラウザはセキュリティ上の理由から外部オリジンからのリクエストを拒否します。

allowCors 関数

Vercel プラットフォームを使用すると、開発者はリクエストの受信時に応答ヘッダーを指定できます。次の allowCors 関数を使うとラッパーとして機能し、渡されるサーバーレス関数の CORS を有効にできます。

const allowCors = (fn) => async (req, res) => {
  res.setHeader('Access-Control-Allow-Credentials', true)
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
  res.setHeader('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version')
  if (req.method === 'OPTIONS') {
    res.status(200).end()
    return
  }
  return await fn(req, res)
}

const handler = (req, res) => {
  const d = new Date()
  res.end(d.toString())
}

module.exports = allowCors(handler)

ソースコードは、How to Enable CORS on Vercelから引用しました。

上記のコードは Next.js の API ルートに CORS を設定する一例です。ここでは、Access-Control-Allow-Origin ヘッダーを設定して、全てのオリジンからのリクエストを許可しています。

next.config.js

もう一つの解決法は、next.config.js で CORS を設定する方法です。こちらの方が簡単です。

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  // 全ての API routes にマッチ
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Credentials', value: 'true' },
          { key: 'Access-Control-Allow-Origin', value: '*' },
          { key: 'Access-Control-Allow-Methods', value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' },
          { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' },
        ],
      },
    ]
  },
}

module.exports = nextConfig

API からのレスポンスの処理

次のコードは、Next.js の API ルートに CORS を設定する一例です。ここでは、Access-Control-Allow-Origin ヘッダーを設定して、全てのオリジンからのリクエストを許可しています。

// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'

const helloAPI = (req: NextApiRequest, res: NextApiResponse) => {
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.status(200).json({ name: 'Hello' })
}

export default helloAPI

フロントエンド側からこの API を呼び出し、レスポンスを受け取る方法を見てみましょう。

// pages/index.tsx
import { useEffect, useState } from 'react'

const HomePage = () => {
  const [message, setMessage] = useState('')

  useEffect(() => {
    fetch('/api/hello')
      .then((res) => res.json())
      .then((data) => setMessage(data.name))
  }, [])

  return (
    <div>
      <p>{message}</p>
    </div>
  )
}

export default HomePage

このコードは、API /api/hello からデータをフェッチし、取得したメッセージを表示するコンポーネントです。

コード解説

それぞれのコードについて解説していきましょう。まず、pages/api/hello.ts では、Access-Control-Allow-Origin ヘッダーを設定し、すべてのオリジンからのリクエストを許可しています。通常、このヘッダーの値は特定のオリジンを指定しますが、この例ではワイルドカード(*)を使用して全てのオリジンからのアクセスを許可しています。

次に、pages/index.tsx では、useEffect フックを使ってコンポーネントのマウント時に API を呼び出しています。fetch 関数で API を呼び出し、その結果を message ステートにセットしています。

この例では、Next.js のサーバーサイドルーティングを利用していますので、CORS の問題は発生しません。しかし、異なるオリジンの API を使用する場合には、CORS の問題が発生する可能性がありますので、対応が必要となります。この記事で解説した方法を参考に、CORS による問題を解決し、安全な Web 開発を行ってください。

CORS は複雑そうに見えますが、理解しておくと大変役立ちます。実際にコードを書いて試してみると、もっと理解が深まると思います。

Next.js と TypeScript で、CORS 問題を解決する