📑

Next.jsをYarn 2のPnP/Zero-Installs + Dockerで動かす

2021/04/17に公開1

Yarn 2ではPnP(Plug'n'Play)という機能が導入され、これに基づくZero-Installsというワークフローが提案されています。

この機能を使っている場合、Next.jsが公式で用意しているDockerfileのサンプルでは動作しません。

本記事では、PnP/Zero-Installsの概要について紹介した上で、Zero-Installsで動くよう改造したDockerfileを紹介します。

なお、本記事で使用しているプロジェクトは下記GitHubリポジトリでも公開しています。
https://github.com/ryo-utsunomiya/nextjs-yarn2-zero-installs

結果だけ知りたい方は Dockerfile を参照してください。

使用しているソフトウェアのバージョン

  • Yarn 1: 1.22.0
  • Yarn 2: 2.4.1
  • Next.js: 10.1.3
  • Node.js: 15.13.0
  • Docker: 20.10.5
  • macOS: 11.2.3

PnPとは

PnP(Plug'n'Play)とは、Yarn 2で導入された、Node.jsモジュールの解決方法です。

Yarn 1では、npmと同様、node_modulesディレクトリに全ての依存を展開して管理していました。この方法にはさまざまな問題があり、解決策として考案されたのがPnPです。

PnPでは、依存関係の解決のために .pnp.js というスクリプトを用意します。このスクリプトをNode.jsアプリケーションの起動時に読み込むことで、node_modulesを使わず、独自の方法でモジュールのロードを行います。

既存の node_modules とは異なるモジュールロードの仕組みを用いることで、従来の node_modules にあった問題(モジュールインストールの遅さ、不安定さなど)を解決することを目指しています。

Zero-Installsとは

Zero-Installsは、PnPに基づいた新しいワークフローで、端的にいうと開発のワークフローで yarn install を使わないことをいいます。

具体例を見た方がわかりやすいので、Yarn 2のZero-Installsを使ってプロジェクトをセットアップする方法を見ていきます。

Yarn 2でプロジェクトを作成する

まず、yarnコマンドをインストールして、プロジェクトを作成し、プロジェクトでYarn 2を使用する設定を行います。2021/04/17現在、Yarnのデフォルトバージョンは1系で、オプションで2系を有効化できるようになっています。

# yarn コマンドをグローバルにインストール( Homebrew 等を使ってもOK )
npm install -g yarn
# yarnのバージョン確認。 1.22.10 など1系のバージョンになるはず
yarn -v
# プロジェクトのディレクトリに移動
cd ~/path/to/project
# Yarn 2を有効化
yarn set version berry
# yarnのバージョン確認。 2.4.1 など2系のバージョンになるはず
yarn -v

yarn set version berry すると、 .yarn ディレクトリが作成され、その中にYarn 2のスクリプトがダウンロードされます。

.yarn
└── releases
    └── yarn-2.4.1.cjs

あわせて .yarnrc.yml というファイルも作成され、ここにYarn 2のスクリプトへのパスが記載されます。

yarnPath: .yarn/releases/yarn-2.4.1.cjs

この状態で yarn init すると、package.jsonに加え、.gitignoreなどいくつかのファイルが生成されます。

.
├── .editorconfig
├── .gitattributes
├── .gitignore
├── README.md
├── package.json
└── yarn.lock

これでYarn 2のプロジェクト作成は完了です。

Next.jsの導入

作成したプロジェクトにNext.jsを導入してみます。

yarn add next react react-dom

これによって、Yarn 1と同様 package.json と yarn.lock が更新されますが、Yarn 1と異なり node_modules は作成されません。

その代わり、 .pnp.js という依存解決のためのスクリプトが生成され、さらに .yarn/cache の中にライブラリがインストールされます。

Zero-Installs では、 .pnp.js 及び .yarn/cache をリポジトリに入れて管理します。これによって、リポジトリからソースコードをチェックアウトすれば必要なライブラリが一式ダウンロードされるので、CI環境等でチェックアウト後にライブラリをインストールするする必要がなくなります。

次に、Next.jsを実行するのに最低限必要な pages/index.js を追加します。

const Index = () => "hello"

export default Index

ついでに package.json によく使うスクリプトを追加しておきましょう。

{
...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
...
}

準備ができたら yarn dev でNext.jsアプリケーションが起動できることを確認します。

Zero-Installsの動作確認

本当にZero-Installsが実現できているか確認するため、別の場所にプロジェクトを作って動かしてみます。

cd /tmp
git clone git@github.com:ryo-utsunomiya/nextjs-yarn2-zero-installs.git
cd nextjs-yarn2-zero-installs

上手くいっていれば、ここで yarn dev を実行すればすぐにNext.jsアプリケーションが起動するはずです。従来のように yarn install を実行する必要はありません。

Dockerfileの改造

Next.jsには公式にサンプルDockerfileが用意されています。これは yarn install の実行を前提としているので、Zero-Installsではそのままでは使えません。

改造の必要なポイントは3点あります: (1) yarn install を使わない (2) Yarn 2を有効化する (3) PnPのためのファイルをコピーする

FROM node:alpine AS builder
WORKDIR /app
COPY . .
# (1) yarn installは不要。buildだけ
RUN yarn build

FROM node:alpine AS runner
WORKDIR /app

ENV NODE_ENV production

# (2) Yarn 2を有効化
COPY --from=builder /app/.yarnrc.yml ./.yarnrc.yml
COPY --from=builder /app/.yarn ./.yarn
# (3) PnPのために必要なファイル
COPY --from=builder /app/.pnp.js ./.pnp.js
COPY --from=builder /app/yarn.lock ./yarn.lock
# 以降は通常のNext.jsに必要なファイル
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/package.json ./package.json

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
RUN chown -R nextjs:nodejs /app/.next
USER nextjs

EXPOSE 3000

CMD ["yarn", "start"]

最後に docker build . -t my-next-js-app でDockerイメージをビルドし、 docker run -p 3000:3000 my-next-js-app でコンテナを立ち上げます。

先ほどと同様、 http://localhost:3000 でアプリケーションが立ち上がることを確認できたらOKです。

以上、Next.jsでYarn 2のZero-Installsを使う方法と、その際に必要なDockerfileについて解説しました。

Discussion

ippolitippolit

Thank you, I spent the whole evening with this problem, and you explained it so simply! 🙏

Hi, from Russia.