Docker環境でNext.jsのSSR実装するとき気をつけること
Docker環境でNext.jsのSSRの実装をしていたら途中ハマったので、解決策を備忘録として残しておきます。
こんな人には役に立つかもしれません。
- Dockerを使っており、フロントエンドとバックエンドでコンテナを分けている
- Next.jsでSSRまたはSSGを実装する
私の開発環境は以下の通り。
- フロントエンド:Next.js (v13.1.6)
- バックエンド:Python, Django REST framework(v3.13.1), PostgreSQL(v14)
フロントエンドとバックエンドでそれぞれDockerコンテナを設けて開発しており、バックエンドではNginxを導入しています。
事の経緯
途中までSSR等使うことなく開発を進めていましたが、追加機能の実装に伴い必要に迫られたのでトップページでSSRを実行しました。
フロントエンドはローカルホスト3000番, バックエンドはlocalhost8000番で開発を進めており、下記のようなコードを加えたところ...
export async function getServerSideProps(context:any) {
const { data } = await axios.get("http://localhost:8000/api/foo/");
return {
props: {
data: data
},
};
}
localhostにアクセスできなくなり、500エラーを返すようになってしまった。
そのときのエラーメッセージとしては下記のようなものです。
Connect ECONNREFUSED 127.0.0.1:8000
色々調べた感じ原因は2つあり、二段階で解決する必要がありそう。
原因と解決策1
ということでまず1段階目。
Docker環境下でSSRする場合はホスト側ではなくコンテナ側のポートを指定する必要がありそう...というわけでそのようにAPIのコール先を書き換える。
例えばバックエンドでnginxを使っているなら docker-compose.yml
には下記のように記述されているんじゃないかと思います。
version: "3.9"
services:
app:
...(省略)
db:
...(省略)
nginx:
ports:
- 8000:80
...(省略)
このときnginxのservice名はnginx、ポート80でリッスンしているので、下記のようにします。
// Before
const { data } = await axios.get("http://localhost:8000/api/foo/");
// After
const { data } = await axios.get("http://nginx:80/api/foo/");
これで1つ目のエラーが解決できました。
ローカルホストにアクセスすると先のエラーメッセージは出なくなります。
が、今度は次のようなエラーが出ます。
Error: getaddrinfo ENOTFOUND nginx
原因と解決策2
Error: getaddrinfo ENOTFOUND nginx
これはコンテナを複数使っていてかつ、ネットワークがそれぞれ異なっていると起きるようです。
というわけで、フロントエンドとバックエンドともに同じネットワークを使うようコンテナの設定を修正します。
まずバックエンド側の docker-compose.yml
に networks
に関する設定を追記します。
version: "3.9"
services:
app:
...(省略)
networks:
- front
- back
db:
...(省略)
networks:
- back
nginx:
...(省略)
networks:
- front
networks:
front:
external: false
back:
external: false
ここでは front
と back
という2つのネットワークをそれぞれ定義し、nginxは front
、dbは back
、appは front
と back
両方のネットワークに接続することを意味します。
これを定義していおくことでコンテナ間の通信が可能になります。
最後の方に記述している external: false
はこれらのネットワーク front
, back
が新規にここで定義されたものであることを明示しています。
もしすでに外部のコンテナで定義されたネットワークを使う場合はそのネットワーク名を記述し、 external: true
とします。(これについては詳細を後述)
続いてフロントエンド側の docker-compose.yml
にもnetworks
に関する設定を追記します。
services:
app:
...(省略)
networks:
- foo_front
networks:
foo_front:
external: true
まずこの foo_front
を説明します(少々ややこしい)。
先程は front
, back
というネットワークを新規に定義しましたが、同一のネットワークを使う必要があるため今回は既存のネットワークを使うよう設定します。
というわけで、先程バックエンドのコンテナでnginxに設定したネットワーク名をこちらのnetworksにも設定します。
ではネットワーク名は front
か?...と思いきや、ちがいます。
デフォルトではプロジェクト名(ディレクトリ名)がネットワーク名のプレフィックスとして使用されプロジェクト名_front
となります。
というわけで、このバックエンドのディレクトリ名が例えば foo
だった場合はfooがプレフィックスとなり、 foo_front
となります。
なお、ネットワーク名はコマンド docker network ls
で確認することもできます。
さらに、先程は新規でネットワークを定義したため docker-compose.yml
の下部にnetworksの設定で external: false
を定義しましたが、今回は先程もうすでに定義したネットワークを使うため external: true
とします。
これにて解決。
※2023/11/17追記
一点付け加え忘れました!
Djangoとnginxを使っている人は今回の変更により、 setting.py
の ALLOWED_HOSTS
に nginx
の追記が必要です。お忘れなきよう
Discussion