Next.jsでクロスオリジンエラーとたたかう


はじめに

30代未経験からエンジニア転職をめざすコーディング初学者のYNと申します。お読みいただきありがとうございます。
初学者が一度はつまづくと思われるCORSエラーについて、自分の学びを本記事にまとめました。

今回の問題

Next.jsを使って開発中、フロントエンドから外部API(今回は自作のAPI)にアクセスしようとするとクロスオリジンエラーが発生する。事象を簡単に図にすると以下の通り。

クロスオリジンエラーとは

エラーについて正しく理解したい方はこちらの記事を参考にしていただくとして、自分の理解をかなりざっくり図にするとこんな感じです。

ブラウザ自身が、ホスティングサーバー以外のドメインにアクセスすることを禁止しているため、フロントエンドから外部APIなどにアクセスしようとするとエラーになる、というのがCORSエラーの正体です。

解決策

上記を見ればお分かりいただけます(?)通り、解決策は2つあります。
* ブラウザちゃんに他の男(ドメイン)と直接会話してもらう
* ブラウザちゃんにバレないように、ホスティングサーバーの方で外部APIと会話して、その結果をブラウザちゃんに伝える。

本記事では、後者について対処法をまとめていきます。

対処法

ブラウザちゃんにバレないように、ホスティングサーバーの方で外部APIと会話して、その結果をブラウザちゃんに伝える。

この対処法を簡単に図にまとめると下記になります。

ブラウザが特定のAPIにアクセスする代わりに、ホスティングサーバー側でAPIとやりとりして、結果をブラウザに返すようにルートを変更します。
これを実現するために、Nextにカスタムサーバーを導入することで対処することができます。

カスタムサーバーを設定する

やることは2つだけです。
まず、rootディレクトリにserver.jsをつくります。

server.js
const express = require("express");
const next = require("next");
const { createProxyMiddleware } = require("http-proxy-middleware");

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.use(
    "/endPoint",
    createProxyMiddleware({
      target: "https://example.amazonaws.com",
      changeOrigin: true,
    })
  );

  server.all("*", (req, res) => {
    return handle(req, res);
  });

  server.listen(port, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://localhost:${port}`);
  });
});


これにより、ブラウザからhttps://example.amazonaws.com/endPointにアクセスすることができます。

次に、package.jsonを変更します。

package.json
"scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  }

一応補足ですが、ブラウザからアクセスするときは、https://example.amazonaws.com/endPointでなく/endPointにアクセスします。つまり、ブラウザとしてはホスティングサーバーにアクセスしているつもりです。

TestComponent.js
import axios from "axios";

export default function TestComponent() {
  useEffect(() => {
    const opt = {
      method: "get",
      url: "/endPoint",
    };
    axios(opt).then((res) => {
      console.log(res.body);
    });
  }, []);
}

最後に

いかがでしたでしょうか。
間違っている点などご指摘いただければ幸いです。

参考にさせていただいた記事

https://qiita.com/att55/items/2154a8aad8bf1409db2b
https://qiita.com/lelouch99v/items/3dc11676bb9c23457d41