🖼️

Next.js Image(next/image)の外部URL指定で502 Bad Gatewayになる

に公開

相当イレギュラーなケースかとは思いますが、半日くらい悩んでしまったので備忘として記載します。

事象

  • Next.js Imageコンポーネントのsrc属性に外部URL(実際は同アプリのRoute handler)を渡すと502 Bad Gatewayで画像の読み込みに失敗する
  • エラーはOPTIMIZED_EXTERNAL_IMAGE_REQUEST_UNAUTHORIZED
  • src属性の外部URLブラウザ直打ちで画像が見られる(※これは罠、後述)
  • Vercel Preview環境にて発生(カスタムドメイン設定済み)
  • next.configのremotePatternsには上記ドメインを追加済み
  • ローカル環境では発生しない( next build && next start した場合も含めて)

外部URLは今回、同Next.jsアプリのRoute Handlerをつかったapiが指定されており、ブラウザ上で確認できるhtmlとしては以下のようなものでした。
src="/_next/image?url=https%3A%2F%2Fdev.example.com%2Fapi%2Fmedia%2Ffile%2Ftest.png%3F2025-09-03T07%3A45%3A58.581Z&w=3840&q=100"

結論

VercelのPreview環境をprivateにしていたため(おそらくProduction以外はこれがVercelのデフォルト設定)、Image Optimizationのアクセスが認証を通れずにブロックされていた。

Production環境はpublicになるため、問題なく画像が読み込めます(一時的にPreview環境の認証をオフして確認済み)。

説明

Next.jsのImageで外部URLを渡すと、Vercel EdgeサーバーからそのURLにfetchを行いリソースを取得するようです。このときCookieや認証ヘッダは渡されないため、privateなエンドポイントだとエラーが発生します。

このように書くと当然のことで悩む余地もなさそうですが、「src属性の外部URLブラウザ直打ちで画像が見られる」という挙動から、外部URLはpublicにアクセスできる状態だと誤認してしまい、原因を理解するのに時間がかかりました。

先述のとおりこれはVercelのPreview環境で、アクセス時にVercelの認証が必要です。ただ、私は同じブラウザでこのドメインにアクセスして挙動確認などをしていたので、画像のURLを直打ちした際も無意識で認証が通っており(おそらくCookie)アクセスできた、ということでした。

なお、これは試しにcurlでアクセスしてみたら401が返ってきたことで気づくことができました。

その他できそうな手段

今回はやらないで済みましたが、本当に解決できなくて困ったら以下を検討していたと思います。

  • Next.js Imageを使わずに素の img タグを使う
  • Next.js Imageの unoptimize をつけて最適化処理をスキップする

学び

  • Next.jsのImage Optimizationはブラウザ(クライアント)でもサーバーでもなく、Vercel Edgeを経由して画像を取得し配信する
  • このとき認証情報は引き継がれないため、外部URLはpublicである必要性
  • 挙動確認はたとえGETでもブラウザとcurl両方やったほうがいい

Discussion