Next.js で CORS エラーを回避してOGPを取得する
■ はじめに
Next.jsで開発しているサービスで、外部サービスのOGPを取得したい場面がありました。
そのときCORSエラーに直面し、Next.jsのAPI Routesでプロキシーサーバーをつくって解決したときの話です。
Next.js の Server Actions を使う方法もある?
API Routesを利用しなくても、
最近出てきた Next.js Server Actions を使えばCORSエラー気にせず実装できるかも。
そこまで試せていないので、今度やってみたい。
次に当てはまる方は読んで価値がある内容かもしれません。
- フロントエンドから外部サービスの
OGPを取得したい -
CORS エラーに直面して困った -
同一オリジンポリシー(Same-Origin Policy)てなんやねん -
プロキシーサーバーてなんやねん
CORSやSOPに関してはこちらの記事も読んでみてください。
■ 直面した問題
OGPデータを取得しようとフロントエンドからfetch APIを利用して外部URLのリソースにアクセスしました。
const result = await fetch(url);
CORSエラーになり、リソースを取得できませんでした。
http://localhost:3000からurlに https://zenn.dev を指定してアクセスしたときのエラー

多くの外部サービスの場合、今回のケースのように別のサービスでブラウザからURLを直接呼び出すとCORSエラーになり、リソースを取得できません。
これはブラウザがデフォルトで持つセキュリティルールの同一オリジンポリシー(Same-Origin Policy)に反するために生じるものです。
つまり今回のケースだと、
表示しているサービスのURL(http://localhost:3000)から、別サービスURL(https://zenn.dev) の情報を取得しようとしているのは、webブラウザのルール違反だからだめだよ、ということでブラウザ側でリクエストが弾かれているイメージです。
// 特定のサイトからのアクセスを許容する場合
Access-Control-Allow-Origin: http://localhost:3000 (アクセスを許可するサイトのオリジン)
// すべてのサイトからのリクエストを許可する場合
Access-Control-Allow-Origin: *
この問題を今回は、プロキシサーバーを用意する方法で解決します。
CORSエラーを回避する方法として、プロキシサーバーを用意する方法は割りと一般的なようです。
■ プロキシサーバーとは
プロキシサーバーの定義
エンドポイントデバイス(Webブラウザやコンピュータなどのリクエストする側のデバイス)と、要求されたサービスを提供する目的地サーバー(リクエストされた側のコンピュータ)間のゲートウェイとして機能するコンピュータまたはシステムのこと。
プロキシとは、英語で「代理」という意味です。
プロキシサーバーは、エンドポイントデバイス(Webブラウザやコンピュータなどのリクエストする側のデバイス)の代わりに、インターネットに接続し、Web上のリソースにアクセスします。

参考:https://nordvpn.com/ja/blog/what-is-a-proxy-server/
プロキシサーバーを介して、別サービスのURLにアクセスすることでブラウザのもつセキュリティルール同一オリジンポリシー(Same-Origin Policy)は関係なくなるので、CORSエラーを回避することができます。
◎ "別オリジン"プロキシサーバーと "同一オリジン"プロキシサーバー
そして、CORSの問題を解決するためのプロキシサーバーは
同一オリジンプロキシサーバー別オリジンプロキシサーバー
2種類のパターンで考えられます。
◯ 別オリジンプロキシサーバー
別オリジンプロキシサーバーは、リクエストを行うwebサイトのURLとは別のオリジンで用意されたサーバーのことです。
例えば、下記のようなwebサイトとプロキシサーバーがあったとします。
このプロキシサーバーは、webサイトと異なるオリジンなので、別オリジンプロキシサーバーです。
- webサイト: https://example.com
- プロキシサーバー: https://example-proxy.com
この場合CORSエラーを回避するためには、
https://example.com からのリクエストを許可するCORSヘッダー
Access-Control-Allow-Origin: https://example.com
をレスポンスに含める設定をする必要があります。
◯ 同一オリジンプロキシサーバー
同一オリジンのプロキシサーバーは、リクエストを行うwebサイトのURLと同じオリジンで用意されたサーバーのことです。
例えば、下記のようなwebサイトとプロキシサーバーがあったとします。
このプロキシサーバーは、webサイトと同じオリジンなので、同一オリジンプロキシサーバーです。
- webサイト: https://example.com
- プロキシサーバー: https://example.com/proxy
同一オリジンプロキシサーバーは同一オリジンポリシー(Same-Origin Policy)のルールに違反しません。
そのため別オリジンプロキシサーバーで行うような、リクエストを許可するCORSヘッダーをレスポンスに含めるなどの設定は不要となります。
■ API Routesで同一オリジンプロキシサーバーをつくる
Next.jsのAPI Routesを使うと簡単に、同一オリジンプロキシサーバーをつくることができます。
つまり、Next.jsのAPI Routesだけで、特段面倒な設定もすることなくCORSエラーを回避できます。
Next.jsプロジェクト内でsrc/pages/api/配下にproxy.tsという、fileを作成します。
proxy.tsという名前は変えても問題ありません。
しかし、file名がそのままエンドポイントになることに注意してください。
Next.jsプロジェクトのホスト名がhttps://example.comだとしたら、エンドポイントはhttps://example.com/api/proxyになります。
import type { NextApiRequest, NextApiResponse } from 'next';
/**
* @description 指定されたurlから取得したHTMLを返す
*/
const proxyApi = async (req: NextApiRequest, res: NextApiResponse) => {
const { url } = req.query;
if (typeof url !== 'string') {
// url が指定されていない場合
return res.status(400);
}
if (req.method !== 'GET') {
// GET 以外のメソッドでアクセスされた場合(GET のみに対応)
return res.status(405);
}
const response = await fetch(url);
const text = await response.text();
res.setHeader('Content-Type', 'text/html');
res.status(200).send(text);
};
export default proxyApi;
https://example.com/api/proxy?url=https://zenn.dev でリクエストされた場合は
req.query.url に https://zenn.dev が入ります。
const proxyApi = async (req: NextApiRequest, res: NextApiResponse) => {
const { url } = req.query;
...
■ プロキシサーバーを介してOGPを取得する
src/pages/api/proxy.tsにつくったプロキシーサーバにリクエストしてOGPを取得する関数をつくります。
今回の例では、OGPのtitleとimageだけを取得しています。
typeやdescriptionなど他のデータを取得したい場合は、適時それに対応させたものを追加してください。
export const getOGP = async (url: string) => {
/**
* src/pages/api/proxy.ts につくったプロキシサーバーにリクエストを送る
* ex) https://example.com/api/proxy?url=https://zenn.dev
*/
const result = await fetch(`/api/proxy?url=${url}`);
const html = await result.text();
// DOM に変換する
const dom = new DOMParser().parseFromString(html, 'text/html');
// head タグの子要素を配列に変換して og:title と og:image を取得する
const data = Array.from(dom.head.children).reduce<{
title: string;
image: string;
}>(
(result, element) => {
const property = element.getAttribute('property');
if (property === 'og:title') {
// title を取得
result.title = element.getAttribute('content') ?? '';
}
if (property === 'og:image') {
// image を取得
result.image = element.getAttribute('content') ?? '';
}
return result;
},
{ title: '', image: '' },
);
return data;
};
■ さいごに
Next.jsめちゃくちゃ有能ですね。
おかげさまでフロントエンドだけでCORSエラーを回避できちゃいました。
(※ 正確にはNext.jsでサーバーサイドをつくっているので、フロントエンドだけではないですが)
参考資料
Discussion