Open8

Next.jsのgetStaticProps、getServersidePropsでDocker環境のLaravel APIにアクセスする時のエラー対応のアレコレ

ゆーたろーゆーたろー

エラー概要

Dockerで構築したNext.js(SPA)+Laravel(API)でNext.jsのページコンポーネントでgetStaticPropsgetServersidePropsを以下のようにして使うとコネクションエラーになる。

コード(例)

export const getStaticProps = async () => {
  const res = await fetch('http://localhost:8080/api/sample')
  const json = res.json()
    return {
      props: {
        data: json
      }
    }
}

エラー文

FetchError: request to http://localhost:8080/api/test failed, reason: connect ECONNREFUSED 127.0.0.1:8080

Docker環境

NginxをRPとしてNext.js、Laravelに処理を振っているわけではなく、localhost:3000でNext.jsで受けて、localhost:8080でNginx経由でLaravel APIにアクセスする構成。
(このエラーとは関係ないですが念の為)

設定ファイルの抜粋

docker-compose.yml
nginx:
  container_name: nginx
 ports:
  - 8080:80

原因

根本的な原因は不明だが

  • クライアント側で使用するのはホスト側(localhost)のポート
  • サーバーサイド側で使用するのはコンテナ側(docker)のポート

を使用しないといけないぽい。

getStaticPropsgetServersidePropsともにサーバーサイドで実行されるのが関係していると思われる。。

解決方法

ローカル側のポートではなく、コンテナ側のポートを指定する(localhost→nginxコンテナのサービス名)

export const getStaticProps = async () => {
  const res = await fetch('http://nginx:80/api/sample')
  const json = res.json()
    return {
      props: {
        data: json
      }
    }
}

なぜこの対応で解決できたのかが腑に落ちないので、調べたことを色々書いていこうと思う。
(React、Next.js、SSG、SSR、Node.jsについての理解がフワフワしていることは自覚している)
もしご存知の方はコメントで教えていただけると非常に嬉しいです!!

ゆーたろーゆーたろー

こちらの記事も参考になる
https://qiita.com/RYO1223/items/515e2537d2232037229b

公式リファレンスが情報源と書いていたけど、それっぽい記述が見つからない...
http://docs.docker.jp/v1.11/compose/networking.html

英語ドキュメントに記載があった

Each container can now look up the hostname web or db and get back the appropriate container’s IP address. For example, web’s application code could connect to the URL postgres://db:5432 and start using the Postgres database.

It is important to note the distinction between HOST_PORT and CONTAINER_PORT. In the above example, for db, the HOST_PORT is 8001 and the container port is 5432 (postgres default). Networked service-to-service communication uses the CONTAINER_PORT. When HOST_PORT is defined, the service is accessible outside the swarm as well.

Within the web container, your connection string to db would look like postgres://db:5432, and from the host machine, the connection string would look like postgres://{DOCKER_IP}:8001.

「各コンテナは、ホスト名 web または db を検索し、適切なコンテナの IP アドレスを返すことができます。例えば、webのアプリケーションコードは、postgres://db:5432というURLに接続し、Postgresデータベースの使用を開始することができます。

HOST_PORTとCONTAINER_PORTの違いに注意することが重要です。上記の例では、dbの場合、HOST_PORTは8001、コンテナポートは5432(postgresのデフォルト)となっています。ネットワーク上のサービス間通信では、CONTAINER_PORTを使用します。HOST_PORTが定義されていると、そのサービスはスウォームの外からもアクセス可能になります。

Webコンテナ内では、dbへの接続文字列はpostgres://db:5432のようになり、ホストマシンからの接続文字列はpostgres://{DOCKER_IP}:8001のようになります。」

ゆーたろーゆーたろー

Next.jsのgetServerSidePropsでLaravel APIで認証済みのユーザー情報を取得できない。

$user = Auth::user(); // null

原因不明😇

なのでgetServerSideProps(getStaticPropsも?)では認証中のユーザーに紐づくデータが取れないっぽいので、useEffectで取得するのがとりあえずの対処法...

ゆーたろーゆーたろー

getServerSidePropsで認証情報を適用する方法の調査結果

NextAuth.js

https://next-auth.js.org/

https://github.com/nextauthjs/next-auth

あとはコレとか

https://github.com/NiclasTimmeDev/laravel-nextjs-starter

以下のファイルに記載されているコメントによると
「APIコールはサーバー上で実行されるため、デフォルトではブラウザに設定されているクッキーはありません。幸いなことに、reqオブジェクトからこれらのクッキーを抽出して、APIコールに添付することができます。」(日本語訳)

  • As the API call is executed on the server it by
  • default does not have the cookies set in the browser.
  • Fortunately, we can extract these cookies from the req object
  • and attach them to the api call.

https://github.com/NiclasTimmeDev/laravel-nextjs-starter/blob/main/client/services/Auth/AuthGuard.tsx

const user = await axios.get("/api/user", {
  headers: { Cookie: req.headers.cookie },
});
ゆーたろーゆーたろー

getServerSideProps getStaticProps もかも)ではAPIで認証情報を判定できないので、
APIのルーティングに認証系のmiddlewareを適用すると認証状態だとしてもmiddlewareに引っかかってしまう。(=未認証と認識される)

上記コメントの通り、ヘッダーを指定すれば多分いける