Next.js のコンテナ化(開発環境にも、本番環境にも対応する)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2291204/b61524bd-2e20-d1d9-a314-7d2f399ff725.png)







# まえがき

自分のいる会社では、会社全体のスキルの底上げを目的として、最近 Next.js をみんなで勉強する取り組みを行っているので、その一環で記事を書きたいと思います。

今回のテーマは Next.js のコンテナ化です。

コンテナ化すると、開発環境の構築も楽になりますし、環境を選ばなくなるので、どのクラウドへもデプロイ可能になるといったメリットがあるかなと思います。

Next.js でも当然コンテナ化したいわけですが、慣れない Next.js をコンテナ化する過程で気づいた点など書いていこうかなと思います。

# はじめに

## Next.jsとは

**Next.js**は、Reactをベースに開発されたフロントエンドフレームワークです。ReactはJavaScript言語を用いてWebサイト上のUIを構築するためのライブラリであり、フレームワークは開発を効率化するための枠組みです。

以下にNext.jsの主な特徴と、Web業界でのトレンドについて詳しく説明します。

### Next.jsの主な特徴

1. **画像・レンダリングの最適化**:
   - Next.jsは画像とレンダリングを最適化してくれます。画像の最適化により、ページの読み込み速度が向上し、SEOにも効果があります。
   - レンダリングにはSSR（Server Side Rendering）、SSG（Static Site Generator）、ISR（Incremental Static Regeneration）の3つの方式をサポートしています。これにより、ページ表示を高速化できます。

2. **ファイルベースルーティング機能**:
   - Next.jsでは、ファイルベースルーティングを利用できます。ファイルを特定のフォルダに配置するだけで、自動的にURLのパスが生成され、アクセス可能になります。
   - 例えば、`/pages/blog.jsx`というファイルを作成すると、`http://localhost/blog`というURLでアクセスできるようになります。

3. **ゼロコンフィグ機能**:
   - Next.jsは、設定を自動で行ってくれるため、開発対象となるプロジェクトに合わせたパッケージのインストールと簡易的な設定だけで動作します。
   - より詳細な設定を変更したい場合は、`next.config.js`ファイルを作成して設定を調整できます。

### Web業界でのトレンド

- **高機能なフレームワーク**:
  - Next.jsは多くの便利な機能を提供し、開発者の生産性を向上させます。
- **SEO対応**:
  - Next.jsのSSRやSSGの機能により、SEOに強力なサポートを提供します。
- **Vercel社のサポート**:
  - Vercel社がNext.jsを開発しており、そのサポートと改良が継続的に行われています。

Web開発者にとってNext.jsは、効率的で高機能な選択肢となっており、今後もさらなる発展が期待されています

## コンテナ化のメリット

そもそもコンテナって何でしたっけ？
AWSの説明が端的でわかりやすかったので、引用します。

https://aws.amazon.com/jp/what-is/containerization/

> コンテナ化は、アプリケーションのコードを、あらゆるインフラストラクチャで実行するために必要なすべてのファイルとライブラリにバンドルするソフトウェアデプロイプロセスです。従来、コンピュータでアプリケーションを実行するには、マシンのオペレーティングシステムに合ったバージョンをインストールする必要がありました。例えば、Windows マシンには Windows バージョンのソフトウェアパッケージをインストールする必要がありました。しかし、コンテナ化を使用することで、あらゆるタイプのデバイスやオペレーティングシステムで実行される単一のソフトウェアパッケージ (コンテナ) を作成できます。 

Next.js とクラウドは技術的に密結合ではないですが、今どきはクラウドにデプロイすることが前提でしょうから、場所を選ばずにアプリが動かせるメリットを享受するために、Next.js のコンテナ化はやりたいところですね。

# **Next.jsのコンテナ化**
## 環境や必要なライブラリ
### 環境

OS: WSL2 の ubuntu 上起動する devcontainer 


```terminal:terminal
// powershell で wsl 起動(Ubuntu を指定)
❯ wsl.exe --distribution Ubuntu

// wsl 起動してシェルにログインしたら、ubuntu を確認
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.6 LTS
Release:        20.04
Codename:       focal
```

### ツール
- wsl2
- docker desktop
- devcontainer
    - コンテナで開発環境を構築する仕組み
    - devcontainer 用コンテナとしては、`mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye` を利用。こちらは、vscode の devcontainer の機能でデフォルトで使えるイメージのうちの一つで、nodejs と typescript が有効になっている

VScode で devcontainer を使うための詳細はこちらから:

https://code.visualstudio.com/docs/devcontainers/containers

devcontainer のクラウドバージョン的な存在の GitHub Codespaces の使い方も記事にしたので良かったらどうぞ

https://qiita.com/EightT/items/7bab30bc682010799ed1

上記の環境で、devcontainer を起動して、そちらで作業しましたが、
ホスト上で直接操作する場合でも同じことかなと思います。
ホスト上でコンテナを触るか、ホスト上のコンテナの上でコンテナを触るかの違いですね！

## Dockerfileの作成

以下のように作ってみます。

```dockerfile
FROM node:20-alpine

WORKDIR /nextjs

COPY . .

RUN npm install
RUN npm run build

CMD ["npm", "start"]
```

なんの変哲も無い Dockerfile です。
ビルドして出来上がるイメージのサイズとかの考慮もゼロ。
シンプルイズベスト。

簡単に説明すると、

1. `COPY . .` でカレントディレクトリにあるファイルを、イメージのカレントディレクトリ(/nextjs) にコピー
1. コピーされたものの中に、package.json もあるので、それをもとに `RUN npm install` を実行。このとき、package.json に next.js が含まれているので、イメージ上で next.js がインストールされます。
1. `RUN npm run build` すると、next.js のビルド機能が実行され、next.js の typescript で書かれたコンポーネントがプロダクションレディな感じに仕上がります。
1. 最後、`CMD ["npm", "start"]` を実行し、そのプロダクションレディなコードでサーバーを起動します。

本当であれば、以下のような工夫が可能ですが、今回特に気にせず、なにも施していません。
- .dockerignore とかを活用して、不要なファイルを除いて、ビルドパフォーマンス向上とセキュリティを向上させる
- マルチステージビルドを使って、アプリケーションを動かすのに必要なファイルだけを最終的に出来上がるイメージに持ってくる
- Next.js のスタンドアロンモードを使って、アプリケーションの軽量化を行う

これらのさらなる工夫やベストプラクティスについては、参考になる記事がたくさんありますが、弊社エンジニアによる以下の記事もとても参考になったので、ぜひ見てみてください。

https://qiita.com/s_sei/items/9019cd2ad9c30f4201a7



## コンテナイメージのビルド

上記の Dockerfile をビルドするコマンド

```bash:terminal
docker build -t my-favorite-docker-image .
```
簡単ですね。
my-favorite-docker-image は適宜お好きな名前でどうぞ！

## コンテナ実行

ビルドしたイメージを実行してみます。
next.js は ポート 3000 を使うので、コンテナ起動時は、3000 のポートフォワーディングも一緒に。

ちなみに、devcontaienr 上で、このようにコンテナを起動すると、ホストの Windows11 まで、ポートが通じます！地味にすごい。

(図解)
Next.js コンテナ 3000 ポート
↑
devcontainer コンテナ 3000 ポート
↑
wsl2 3000 ポート
↑
windows 11 上のWebブラウザ 3000 ポート
(図解おわり)


```bash:terminal
$ docker run -itd -p 3000:3000 my-favorite-docker-image
36db1fda8ddd7e70c667d0aa3bda47a3bdb97db2e8fbe64fbd425e99764b194e
```

コンテナが起動したことを確認し、Next.js にアクセスしてみます。

```bash:terminal
$ docker ps
CONTAINER ID   IMAGE                      COMMAND                  CREATED         STATUS        PORTS                    NAMES
36db1fda8ddd   my-favorite-docker-image   "docker-entrypoint.s…"   2 seconds ago   Up 1 second   0.0.0.0:3000->3000/tcp   bold_burnell
```

ただのサンプルページが開けました。

![wsl2terminal.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2291204/c2b94fa8-4d15-6cc5-4862-38d9ac04e022.gif)


ちなみに、ページ上の虹色に輝いている部分は、以下のライブラリ使ってます。ドロップインで使えるコンポーネントなので、ページに彩りを添えたいときなどにどうぞ！

https://uvcanvas.com/

コンテナ化、簡単ですね！🍀

# 少し困ること

コンテナ化したはいいものの、開発中はバンバンソースコード変えますよね・・・例えば、上記の動作確認したページだと、もっとカードコンポーネント追加したいなとか、見出しの位置変えたいなとか、DBとつなげて、コンテンツを動的に変えたいなとか。

その都度、docker build すると、開発のイテレーション速度が落ちます！困ります！

Next.js には Fast Refresh という機能があり、ソースコードを書き換えたら、そのファイルの変更が自動で検知されて、Webブラウザでの表示も変わるのですが、この機能は、next.js の開発サーバーでのみ有効な機能となっています。(npm run dev で動かせるやつです)
Dockerfile ではクラウドなどへのデプロイを考慮して、`CMD ["npm", "start"]` としているので、このホットリロードな機能が使えなくなってしまうのです。

↓Fast Refresh について詳細

https://nextjs.org/docs/architecture/fast-refresh

## じゃあどうしよう

docker-compose.yml でNext.jsのプロジェクトをボリュームマウントすれば解決です！

例えば、docker-compose.yml を置く際のフォルダ構成がこういうとき

```bash:terminal
$ tree -L 2 .
.
├── docker-compose.yml
└── nextjs
    ├── app
    ├── Dockerfile
    ├── next.config.mjs
    ├── package.json
    ├── package-lock.json
    ├── postcss.config.js
    ├── README.md
    ├── tailwind.config.ts
    └── tsconfig.json
```

docker-compose.yml は次の通り書きます。

```yaml
services:
  nextjs:
    build:
      context: nextjs
    ports:
      - 3000:3000
    volumes:
      - ./nextjs:/nextjs
    command: npm run dev
```

ポイントは、2つです。
- `./nextjs` フォルダのボリュームマウント
- command プロパティの npm run dev で Dockerfile のCMDをオーバーライド

Dockerfile では `CMD ["npm", "start"]` となっていたものを、command プロパティで、`npm run dev` に上書くことで、ローカルの開発環境では、next.js の開発サーバーを使うようにします。
また、開発サーバーだと前述の通り、Fast Refresh が使えるようになるので、ボリュームマウントしたフォルダのファイルをホスト上で変更しても、コンテナにもそれが反映され、ブラウザで変更が確認できるようになります。

# まとめ
上記のようなやり方で、サクッととりあえず、開発環境にも対応しつつ、一応クラウドとかにデプロイもし易い形での Dockerfile が用意できました。

ただ、マルチステージビルド使えば、いいんじゃ・・・と思ったんで、またそれは別の記事書くときに調べ直そうと思います。