Next.js の開発は npm run dev で始めますが、実際にサイトを公開するときに使うのは next build と next start です。開発サーバーとは動き方がまったく違うため、「ローカルでは動いたのにビルドで落ちる」「next start を実行したらエラーになった」といったつまずきも起きやすいところです。この記事では App Router を前提に、この2つのコマンドが何をしているのか、ビルドログの読み方、失敗したときに見る場所までを順番に解説します。
目次
next dev と next build・next start は役割が違う
Next.js のコマンドは大きく「開発用」と「本番用」に分かれます。next dev は開発専用のサーバーで、ソースコードを変更するとその場でコンパイルし直し、画面に即座に反映します。書き換えるたびに動作を確かめられる代わりに、コードの最適化はほとんど行われません。
一方、本番は next build と next start の2段階です。まず next build でアプリ全体をまとめてコンパイル・最適化し、その成果物をディスクに書き出します。次に next start が、その書き出された成果物を配信するサーバーを立ち上げます。つまり next start は単体では動かず、必ず先に next build が必要ということです。
| コマンド | 役割 |
|---|---|
next dev | 開発サーバーを起動する。ファイル変更を検知して即座に反映(HMR)。最適化はしない |
next build | 本番用にコンパイル・最適化し、成果物を .next に書き出す。サーバーは起動しない |
next start | .next の成果物を配信する本番サーバーを起動する。実行前に next build が必要 |
「本番と同じ状態を手元で確認したい」というときは、開発サーバーではなく next build と next start の組み合わせで確認します。ページが静的に配信されるかどうか、画像やフォントの最適化がどう効くかといった挙動は、開発サーバーでは再現されないためです。
package.json の scripts から呼び出す
create-next-app でプロジェクトを作ると、package.json の scripts にこれらのコマンドが最初から登録されています。実際に next コマンドを直接打つ機会は少なく、ふだんは npm run dev のように npm スクリプト経由で実行します。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
本番の動きを手元で確認する手順は次のとおりです。ビルドしてから起動する、という順番だけ守れば大丈夫です。
# 1. 本番用にビルドする npm run build # 2. ビルド結果を配信するサーバーを起動する npm run start
起動に成功すると http://localhost:3000 でアクセスできます。ここで表示されているのは開発サーバーではなく、本番と同じ最適化済みのコードです。
next build が .next ディレクトリに書き出すもの
next build の成果物は、プロジェクト直下の .next ディレクトリに出力されます。ここにはサーバー側で実行される JavaScript、ブラウザに配信される JS/CSS、事前に生成された HTML、そしてどのルートをどう配信するかを記録した設定ファイルなどが入っています。
next start はこの .next を読み込んで動作します。裏を返すと、.next が無い状態、あるいは古い状態のままだと next start は正しく動きません。ビルドせずに next start を実行すると、ビルドが見つからない旨のエラーが出て終了します。
Error: Could not find a production build in the '.next' directory. Try building your app with 'next build' before starting the production server.
.next は自動生成されるディレクトリなので、Git で管理する必要はありません。create-next-app が用意する .gitignore にも最初から /.next/ が含まれています。コードを書き換えたら必ずビルドし直す、という点さえ意識しておけば十分です。挙動がおかしいときに .next を丸ごと削除してビルドし直すのも、よく使う切り分け方法です。
ビルドログの Route 一覧を読む
next build が終わると、ターミナルにルートの一覧が表示されます。これは単なる完了メッセージではなく、どのページが静的に生成され、どのページがリクエストごとに生成されるのかを確認できる重要な情報です。
Route (app) Size First Load JS ┌ ○ / 142 B 87.3 kB ├ ○ /about 138 B 87.3 kB ├ ● /blog/[slug] 1.2 kB 88.4 kB ├ ├ /blog/hello-world ├ └ /blog/nextjs-build └ ƒ /dashboard 1.5 kB 88.7 kB + First Load JS shared by all 87.2 kB ○ (Static) prerendered as static content ● (SSG) prerendered as static HTML (uses generateStaticParams) ƒ (Dynamic) server-rendered on demand
行頭に付いている記号がレンダリング方法を表しています。凡例はビルドログの下部にも毎回出力されますが、意味は次のとおりです。
| 記号 | 表示名 | 意味 |
|---|---|---|
○ | Static | ビルド時に静的な内容として事前生成される |
● | SSG | generateStaticParams を使い、パラメーターごとに静的な HTML を事前生成する |
ƒ | Dynamic | リクエストのたびにサーバーでレンダリングされる |
静的にしたつもりのページが ƒ(Dynamic)になっていたら、そのページのどこかで動的な API を使っている可能性があります。cookies() や headers() を読んだり、searchParams を参照したりすると、Next.js はそのルートを動的なものとして扱います。意図と違う記号が付いていないかをビルドのたびに眺めておくと、パフォーマンスの問題に早く気づけます。
右側の「Size」はそのルート固有の JavaScript サイズ、「First Load JS」は共有分も含めた初回読み込み時の合計サイズです。ページを追加したのに First Load JS が急に増えたときは、重いライブラリをクライアント側に取り込んでしまっていないかを疑うきっかけになります。
ビルドが失敗するときに見る場所
開発サーバーでは動いていたのに next build だけが失敗する、というのはよくあることです。開発サーバーは表示中のページだけを都度コンパイルするのに対し、ビルドはアプリ全体をまとめて検査するため、普段開いていないページの問題まで一度に表面化するからです。
TypeScript の型エラーでビルドが止まる
TypeScript を使っている場合、next build は型チェックを実行し、エラーが1つでもあればビルドを中断します。エディター上では赤い波線が出ているだけで動いてしまうので、ビルドで初めて気づくケースが多い部分です。
原則としてエラーは直すべきですが、どうしても一時的にビルドを通したい事情があるときは、next.config.ts で型エラーを無視する設定にできます。ただし型の不整合をそのまま本番へ持ち込むことになるため、公式ドキュメントも危険な設定として扱っています。使う場合は一時的な回避策と割り切ってください。
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
typescript: {
// 型エラーがあってもビルドを続行する(原則として非推奨)
ignoreBuildErrors: true,
},
eslint: {
// ビルド時の ESLint チェックをスキップする
ignoreDuringBuilds: true,
},
};
export default nextConfig;
ESLint のエラーで止まることもある
ESLint がプロジェクトに設定されている場合、ビルド時にリントが走り、エラーレベルの指摘があるとビルドが失敗します。警告(warning)では止まらず、エラー(error)だけが対象です。上のコードにある eslint.ignoreDuringBuilds を true にすると、このチェックをスキップできます。
なお、ビルド時に ESLint を実行するかどうかは Next.js のバージョンによって扱いが変わっています。手元のビルドログに「Linting」の行が出るかどうかで、実際に走っているかを確認できます。CI で品質を担保する運用にしているなら、ビルドとは切り離して eslint を別ステップで実行する形にしておくと分かりやすくなります。
ビルド中にサーバーコンポーネントが実行される
静的に生成されるページは、ビルド中に実際にレンダリングされます。そのためページ内でデータ取得をしていれば、ビルドしているマシンからそのリクエストが飛びます。API のエンドポイントに到達できなかったり、必要な環境変数が設定されていなかったりすると、その時点で例外が発生してビルドが失敗します。
「ローカルでは通るのに CI やデプロイ先だけ失敗する」という場合は、まずこのパターンを疑ってください。ビルド環境に環境変数が渡っているか、ビルド環境からその API に接続できるか、という2点を確認するのが近道です。
NEXT_PUBLIC_ の値はビルド時に埋め込まれる
環境変数の扱いも、next build のタイミングと深く関わります。NEXT_PUBLIC_ で始まる環境変数は、ブラウザに配信される JavaScript の中でビルド時に実際の値へ置き換えられます。変数として実行時に読み込まれるのではなく、文字列としてコードに焼き込まれるイメージです。
// ソースコード const url = process.env.NEXT_PUBLIC_API_URL; // ビルド後の出力(値が直接埋め込まれる) const url = 'https://api.example.com';
ここから分かるのは、NEXT_PUBLIC_ の値を変えたら必ずビルドし直す必要があるということです。next start のときだけ環境変数を差し替えても、すでに埋め込まれた値は変わりません。同じビルド成果物を本番とステージングで使い回す、といった運用ができない点にも注意してください。
逆に NEXT_PUBLIC_ が付かないサーバー専用の環境変数は、サーバー側の実行時に読み込まれます。とはいえ、静的生成されるページの中で使われた値はビルド時に確定した結果が HTML に反映されるため、こちらもビルドのタイミングを意識しておく必要があります。
next start のオプションとポートの変更
next start は既定でポート 3000 を使います。すでに別のアプリが 3000 番を使っていたり、1台のサーバーで複数の Next.js アプリを動かしたりする場合は、-p オプションでポートを変更します。
# ポート 8080 で起動する npx next start -p 8080 # 外部からアクセスできるように、待ち受けるホスト名も指定する npx next start -H 0.0.0.0 -p 8080
npm スクリプト経由で渡したいときは、npm run start -- -p 8080 のように -- を挟みます。よく使うオプションは次のとおりです。
| オプション | 意味 |
|---|---|
-p, --port | 待ち受けるポート番号を指定する(既定は 3000) |
-H, --hostname | 待ち受けるホスト名を指定する(既定は localhost) |
--keepAliveTimeout | 接続を維持する時間をミリ秒で指定する。前段のプロキシに合わせて調整する |
ポート番号は環境変数 PORT でも指定できます。ホスティング側がポートを指定してくる構成では、こちらを使うほうが素直です。-p と PORT の両方を指定した場合は -p が優先されます。
output オプションで出力の形を変える
next.config.ts の output を指定すると、next build が書き出すものそのものを変えられます。デプロイ先の要件に合わせて選ぶ設定です。
output: ‘standalone’ で自己完結した server.js を作る
standalone を指定すると、ビルド時に .next/standalone ディレクトリが作られ、その中に実行に必要な最小限のファイルと server.js が出力されます。実行に必要な node_modules だけが自動的にコピーされるため、Docker イメージを小さくしたい場合に向いています。
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;
この構成では next start ではなく、出力された server.js を Node.js で直接実行します。ただし静的ファイルは自動でコピーされないため、.next/static と public を手動で配置する必要があります。
npm run build # 静的ファイルを standalone 配下へコピーする cp -r public .next/standalone/public cp -r .next/static .next/standalone/.next/static # next start ではなく server.js を直接実行する node .next/standalone/server.js
output: ‘export’ では next start が使えない
export を指定すると、next build はサイト全体を静的な HTML・CSS・JS として out ディレクトリに書き出します。Node.js のサーバーを必要としないので、レンタルサーバーや静的ホスティングにそのままアップロードできます。
ここで押さえておきたいのが、静的エクスポートでは next start を使わないという点です。配信するのは Next.js のサーバーではなく、任意の静的ファイルサーバーの仕事になります。手元で確認したいときは npx serve out のようなコマンドを使います。
また、サーバーが無い前提の出力なので、リクエストごとの動的レンダリングやサーバー側の処理を必要とする機能は使えません。ビルド時に静的化できないルートがあると、その時点でエラーになります。サイトの性質に合わせて選択してください。
Vercel などでは build コマンドだけ設定すればよい
ここまで next start を中心に説明してきましたが、Vercel にデプロイする場合、自分で next start を実行することはありません。プラットフォーム側が next build の出力を解釈して配信の仕組みを用意するため、設定するのはビルドコマンドだけです。多くの場合は package.json の build スクリプトが自動的に検出されます。
next start が必要になるのは、自分で Node.js のプロセスを管理する構成のときです。VPS 上で動かす、Docker コンテナとして動かす、社内サーバーに置く、といったケースが該当します。この場合はプロセスマネージャーやコンテナのコマンドとして next start(あるいは standalone の server.js)を指定し、前段のリバースプロキシからそのポートへ転送する形が一般的です。
どちらの構成でも、デプロイのたびに next build が実行されるという点は共通です。自前でサーバーを運用する場合は、ビルドが終わってからプロセスを入れ替える順番を意識しておくと、切り替え中のエラーを避けられます。
まとめ
next dev は開発専用のサーバー、next build は本番用の成果物を .next に書き出すコマンド、next start はその成果物を配信するサーバーです。next start は単体では動かず、必ず先にビルドが必要になります。本番と同じ挙動を確認したいときは、開発サーバーではなくこの2つを順番に実行してください。
ビルドは型エラーや ESLint のエラー、そして静的生成中のデータ取得の失敗で止まります。ログの Route 一覧では ○・●・ƒ の記号で各ルートのレンダリング方法が分かるので、意図しない動的レンダリングに気づく手がかりになります。NEXT_PUBLIC_ の環境変数はビルド時に値が埋め込まれるため、変更したら再ビルドが必須です。デプロイ先については、Vercel のようなプラットフォームならビルドコマンドの設定だけで済み、自前でサーバーを持つなら next start か output: 'standalone' の server.js、静的ホスティングなら output: 'export' と使い分けます。