【Next.js】standalone機能のビルドサイズを通常ビルドと比較する

どうも、インターンの手塚です。今回は、Next.jsのstandaloneという機能に焦点を当てた記事を書こうと思います。standalone機能がビルドサイズをどれだけ小さくするのかを確認してみましょう。

目次

standalone機能とは

standalone公式ドキュメント

// next.config.js
module.exports = {
  output: 'standalone',
}

公式ドキュメントにもあるように、next.config.jsに上のように書くことでstandalone機能が有効になります。このモードが有効になった状態でビルドすると、.nextディレクトリ下にstandaloneフォルダが作成されます。このフォルダの下には、node_modulesから、使用するファイルのみがコピーされ、さらにnext startコマンドの代わりに使用できる最小限のserver.jsファイルが生成されます。

要するに、自動的にstandaloneフォルダが作成され、その中に動作に必要な最小限のファイル群がコピーされるという便利な機能です。この機能によってビルドサイズを削減できます。

いざビルド

自分が個人的に運営しているブログを実際に2通りの方法でビルドして確認してみたいと思います。参考までに、ブログの依存関係は下の通りです。

// package.json
"dependencies": {
    "autoprefixer": "^10.4.7",
    "copy-webpack-plugin": "^11.0.0",
    "gray-matter": "^4.0.3",
    "markdown-it": "^13.0.1",
    "markdown-it-anchor": "^8.6.4",
    "markdown-it-container": "^3.0.0",
    "markdown-it-emoji": "^2.0.2",
    "markdown-it-prism": "^2.2.4",
    "markdown-it-table-of-contents": "^0.6.0",
    "next": "12.1.6",
    "react": "18.1.0",
    "react-dom": "18.1.0",
    "write-file-webpack-plugin": "^4.5.1"
  }

特に複雑でもない、Next.js製のよくある静的な個人ブログです。

standalone機能を無効にしてビルド

まずは、standalone機能を無効にした状態でビルドしてみます。

# Dockerfile
FROM node:16 AS builder
WORKDIR /app
COPY . .
RUN yarn install --frozen-lockfile --production=false
RUN yarn build

FROM node:16 AS runner
WORKDIR /app
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY package.json ./
COPY yarn.lock ./
RUN yarn install --frozen-lockfile --production=true
CMD ["yarn", "start"]

# in package.json
# scripts": {
#    "start": "next start",
#  },

standalone機能を無効にしているので、本番用にビルドしたnode_moduleをそのままビルドに含めます。

docker build -t not_standalone .
docker run -p 3000:3000 not_standalone

ビルドして、ブラウザからアクセスができることを確認します。

standalone機能を使用しない場合のイメージサイズは、2.09GBでした。

standalone機能を有効にしてビルド

続いて、standalone機能が有効な状態でビルドをしてみたいと思います。

# Dockerfile
FROM node:16 AS builder
WORKDIR /app
COPY . .
RUN yarn install --frozen-lockfile
RUN yarn build

FROM node:16 AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/public ./standalone/
COPY --from=builder /app/.next/static ./standalone/.next/
CMD ["node", "server.js"]

公式のDockerfileを参考にしながらDockerfileを作成します。standalone機能を有効にしているので、本番用にビルドしたnode_moduleをビルドに含める必要がありません。

public.next/staticなどの静的なファイルは通常CDNによって配布される想定なので、自動的にはstandaloneフォルダの下にはコピーされませんが、手動でこれらをstandalone/publicstandalone/.next/staticにコピーすることで、文字通りstandaloneフォルダの中のみでアプリを動作させることが可能になります。

docker build -t standalone .
docker run -p 3000:3000 standalone

ビルドして、ブラウザからアクセスができることを確認します。

standalone機能を有効にした時のイメージサイズは、956.74MBでした。

結果を比較

standalone機能が無効な状態だと: イメージのサイズが2.09GB

standalone機能が有効な状態だと: イメージのサイズが956.74MB

と大幅にビルドのサイズが小さくなっていることがわかりました。アプリが正常に動作するならビルドサイズは小さいに越したことはありませんので最高に嬉しいですね!

最後に

クラウドのコンテナレジストリサービスを使っている人は、イメージのサイズで料金が変わってきたりすることもあるかと思います。DockerとNext.jsを用いてアプリをデプロイしている人は、ぜひstandalone機能を活用してみてください!!!