自分のサーバーに置く。standalone と Docker
output を standalone にすると、server.js だけで動く出力ができますが、.next/static と public は入りません。
standalone が出すもの、出さないもの
next.config.ts に output: 'standalone' と書いてビルドすると、.next/standalone ができます。node_modules を丸ごと持っていかなくても、server.js だけで起動できる形です。
中身を実機で確かめました。
| 入っているか | |
|---|---|
| server.js / package.json | 入っている |
| 使っている node_modules だけ | 入っている |
| .next(サーバー側の出力) | 入っている |
| .next/static(CSS と JavaScript) | 入っていない |
| public(画像など) | 入っていない |
公式ドキュメントによると、静的なファイルは本来 CDN から配るものなので、既定では写さない作りです。自分のサーバーだけで配るなら、自分で写します。
写し忘れると、ページは出るのに壊れている
.next/static と public を写さずに server.js を起動しました。
| 取りに行ったもの | 写さない | 写す |
|---|---|---|
| ページの HTML | 200 | 200 |
| CSS | 404 | 200 |
| JavaScript | 404 | 200 |
| public のファイル | 404 | 200 |
Dockerfile の形
ビルドする段と、動かす段を分けます。動かす段には standalone と、写す2つだけを置きます。
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npx next build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
CMD ["node", "server.js"]
写す先は、server.js から見て .next/static と public です。server.js はそこにあるファイルを自分で配ります。
実機で、この Dockerfile のままビルドして動かし、ページも CSS も public のファイルも 200 で返ることを確かめました。そのとき .dockerignore に node_modules と .next を書いて、手元のものがビルドに混ざらないようにしています。
このサイトも、同じ形の Dockerfile で自分のサーバーに置いています。
HOSTNAME を指定しないと、中からつながらない
server.js は、環境変数の PORT(無ければ 3000)と HOSTNAME(無ければ 0.0.0.0)で待ち受け先を決めます。生成された server.js の中身で確かめました。
ところが Docker は、コンテナの中の HOSTNAME に自動でコンテナの ID を入れます。実際に Docker で動かして比べました。
| 外から(ポート経由) | コンテナの中から 127.0.0.1 | |
|---|---|---|
| HOSTNAME を指定しない | 200 | つながらない |
| HOSTNAME=0.0.0.0 | 200 | つながる |
指定しないと、起動のログにも http://<コンテナの ID>:3000 と出ます。外からは見えるので、動いているように見えます。困るのは、コンテナの中から localhost に問い合わせるヘルスチェックだけが失敗するときです。
ENV HOSTNAME=0.0.0.0 を書いておけば、どちらからもつながります。
next start ではなく node server.js
standalone にしたまま next start で起動すると、こう警告されます。
⚠ "next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.
実機では、警告を出したうえでページは返りました。動いてしまうので、standalone を使っていないことに気づきにくくなります。本番は node server.js で起動します。
環境変数はビルドのときに決まるものがある
Docker で環境変数を実行時にだけ渡すと、事前に描くページには undefined が焼き付きます。NEXT_PUBLIC_ の付いた変数も、ビルドのときの値で固まります(環境変数はいつ読まれるのか)。
どの値をビルドのときに渡し、どの値を実行時に渡すかを、Dockerfile を書く前に分けてください。
入れ子のリポジトリでは server.js の場所が変わる
実機で、リポジトリの中に入れ子になったプロジェクトをビルドすると、server.js が .next/standalone の直下ではなく、リポジトリの根からの相対位置(.next/standalone/<入れ子のパス>/server.js)に置かれました。
Dockerfile の CMD がそのままでは見つからなくなります。複数のプロジェクトを1つのリポジトリで持っているなら、ビルドしたあと server.js がどこに出たかを一度確かめてください。公式ドキュメントは、追跡の起点を outputFileTracingRoot で指定できるとしています。
出す前の確認
.next/standaloneにserver.jsがあるか(入れ子なら場所も).next/staticとpublicを写したかHOSTNAME=0.0.0.0を渡しているか- ビルドのときに渡すべき環境変数を渡したか
- ブラウザで開いて、見た目とボタンが動くか
出典
- nextjs.org/docs/app/api-reference/config/next-config-js/output
- nextjs.org/docs/app/getting-started/deploying
- nextjs.org/docs/app/guides/self-hosting
このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。