🏗️

エンジニアが知っておきたい「npm run build」の全て!

2025/02/06に公開

エンジニアが知っておきたい「npm run build」の全て!

はじめに

今回は、普段何気なく実行しているnpm run buildについて、Next.js 14での実際のビルド結果を見ながら解説します。
特に、Vercelへのデプロイ前に確認すべきポイントを重点的に説明します。

npm run buildとは?

開発環境(localhost:3000)で作成したコードを本番環境で動作させるために必要な変換処理です。
Next.js 14のプロジェクトでは、主に以下の処理を行います。

  • TypeScript(v5.0以上)のコードをJavaScriptへコンパイル
  • ReactコンポーネントをNext.jsのチャンクファイルへバンドル化
  • 画像をWebP形式へ変換(最大80%の圧縮率)
  • 静的ページの生成(SSG)とキャッシュの作成

重要な専門用語の解説

Fast Refresh

Next.jsの開発環境で使用される高速な更新機能です。

  • コードを保存すると、変更された部分のみを自動で再読み込み(約0.3秒)
  • Reactコンポーネントの状態(state)を保持したまま更新
  • エラーが発生しても開発サーバーが停止せず、エラー画面を表示
// Fast Refreshが効く例(src/components/Button.tsx)
export const Button = () => {
  const [count, setCount] = useState(0)  // この値は更新時も保持される
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

コンパイラの違い(webpack vs swc)

Next.js 14では2種類のコンパイラを選択できます:

  1. webpack(デフォルト)

    • 歴史が長く(2012年~)、プラグインが豊富(約8,000個)
    • ビルド時間:中規模プロジェクト(100ページ)で約180秒
    • メモリ使用量:約2GB
  2. SWC(Speedy Web Compiler)

    • Rustで書かれた新しいコンパイラ(2019年~)
    • ビルド時間:中規模プロジェクトで約45秒(webpackの4倍高速)
    • メモリ使用量:約800MB

設定方法:

// next.config.js
module.exports = {
  // swcを有効化
  swcMinify: true,
  // webpackを使用する場合
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    return config
  },
}

App RouterとPages Routerの違い

App Router(Next.js 13.4以降)

src/
  ├── app/           # App Router
  │   ├── layout.tsx    # 共通レイアウト
  │   ├── page.tsx      # トップページ(/)
  │   └── about/
  │       └── page.tsx  # Aboutページ(/about)

ビルド時の特徴:

  • サーバーコンポーネントがデフォルト(JavaScriptバンドルサイズが約40%削減)
  • 静的・動的レンダリングの自動最適化
  • ビルド時間:Pages Routerと比べて約20%増加

Pages Router(従来型)

src/
  ├── pages/         # Pages Router
  │   ├── _app.tsx     # 共通レイアウト
  │   ├── index.tsx    # トップページ(/)
  │   └── about.tsx    # Aboutページ(/about)

ビルド時の特徴:

  • クライアントコンポーネントがデフォルト
  • getStaticProps/getServerSidePropsによる明示的なデータフェッチ
  • ビルド時間:App Routerと比べて約20%短い

ビルド時の挙動の違い

  1. App Router
# ビルドログの例
 ✓ Creating an optimized production build
 ✓ Compiled successfully
 ✓ Generating static pages (6/6)
   ├─ /                              # サーバーコンポーネント
   ├─ /about                         # 静的ページ
   └─ /_not-found                    # 404ページ
 ✓ Collecting build traces
  1. Pages Router
# ビルドログの例
 ✓ Creating an optimized production build
 ✓ Compiled successfully
 ✓ Generating static pages (4/4)
   ├─ / (ISR: 60 seconds)           # インクリメンタル静的再生成
   ├─ /about                        # 静的ページ
   └─ 404                           # 404ページ
 ✓ Finalizing page optimization

package.jsonでの設定例

{
  "scripts": {
    "dev": "next dev --port 3000",
    "build": "next build",
    "build:prod": "NODE_ENV=production ANALYZE=true next build",
    "start": "next start --port 3001",
    "lint": "next lint --dir src"
  }
}

各スクリプトの使い分け

  • npm run dev: ローカル開発時に使用。localhost:3000で起動し、Fast Refresh対応
  • npm run build: 検証環境(dev/stg)デプロイ時に使用。基本的な最適化を実行
  • npm run build:prod: 本番環境デプロイ時に使用。@next/bundle-analyzerでバンドルサイズを解析(バンドルサイズを解析するには、事前に npm install @next/bundle-analyzer の実行が必要です
  • npm run start: AWS EC2やGCP Compute Engineで運用する場合に使用。3001ポートで起動
  • npm run lint: GitHub PRを出す前に実行。srcディレクトリ配下のコードをチェック

一般的なビルドプロセス

実際のNext.js 14プロジェクトでは、以下の順序で処理が行われます。

  1. 依存関係の解決(約10-30秒)

    • node_modules(通常2-3GB)内のパッケージを読み込み
    • package-lock.jsonとの整合性チェック
  2. TypeScriptのトランスパイル(約5-10秒)

    // 変換前(src/components/Heading.tsx)
    const Heading: React.FC<{title: string}> = ({title}) => <h1>{title}</h1>
    
    // 変換後(.next/server/chunks/123.js)
    const Heading=({title:e})=>react_.createElement("h1",null,e)
    
  3. コードの最小化(約10-20秒)

    // 変換前(src/utils/calculate.ts)
    export function calculateTotalPrice(price: number, taxRate: number = 0.1) {
      return price + (price * taxRate);
    }
    
    // 変換後(.next/static/chunks/456.js)
    export const c=(p,t=.1)=>p+p*t
    
  4. 不要コードの削除(約3-5秒)

    // 変換前(src/pages/index.tsx)
    import { formatDate, formatPrice, formatPhone } from '@/utils/formatters';
    const price = formatPrice(1000);  // formatDateとformatPhoneは未使用
    
    // 変換後(.next/static/chunks/789.js)
    import{b as formatPrice}from"./formatters.js";const price=formatPrice(1e3)
    
  5. 画像の最適化(約15-30秒)

    • JPG/PNGをWebPに変換(品質85%で約60-80%のサイズ削減)
    • 画像を3つのサイズ(640px, 750px, 828px)に自動リサイズ

メリット・デメリット

メリット

  • 本番環境用の最適化

    • JavaScriptファイルを最大70%圧縮(例:1MBのコードが300kBに)
    • 画像を自動的にWebP形式に変換(JPEGと比べて約30%軽量化)
    • 未使用のCSSを削除(Tailwind CSSの場合、約95%削減可能)
  • 配信効率の改善

    • ページの初期読み込み時間が約40%短縮(Lighthouse scoreで90+を達成)
    • CDN(Vercel/Cloudflare)でのキャッシュ効率が向上
    • モバイル回線(4G)での読み込みが2秒以内に
  • ブラウザ互換性の確保

    • 最新のTypeScript(v5.0)のコードを古いブラウザ(IE11)でも動作
    • ES6+の機能(async/await等)を自動的にES5に変換
    • CSS Grid/Flexboxのベンダープレフィックスを自動付与
  • コードの品質管理

    • ESLintで一般的なバグを事前検出(約80%のバグを防止)
    • TypeScriptの型エラーを100%検出
    • consoleやdebuggerの本番環境への混入を防止
  • チーム開発の効率化

    • CIでのビルドチェックで問題のあるPRを早期発見(約30分の時間節約)
    • 共通の開発環境(node v18.17.0以上)を強制
    • コードフォーマット(Prettier)の統一を確認

デメリット

  • ビルド時間の長さ

    • 初回ビルドに3-5分かかる(node_modulesのサイズが2GB超の場合)
    • Vercelの無料プランだと1回のビルドで約320秒の制限
    • M1 MacBookと比べてWindowsは約1.5倍の時間が必要
  • 複雑な設定要件

    • next.config.jsの設定が複雑(webpack/swcの理解が必要)
    • 環境変数(.env)の管理が面倒(dev/stg/prod×API_KEY等)
    • サードパーティライブラリとの相性問題(特にMaterial UI v4)
  • デバッグの難しさ

    • ソースマップの生成で容量が約2倍に
    • 本番ビルドでのエラーがローカルで再現しづらい
    • Chromeの開発者ツールでの追跡が難しい(コード難読化のため)
  • リソース消費

    • メモリ使用量が4GB以上(M1 Mac 8GBだとスワップ発生)
    • node_modulesのディスク容量が2-3GB必要
    • CI/CDの実行時間がGitHub Actionsの無料枠(2000分/月)を圧迫
  • 学習コスト

    • webpack/Babelの基礎知識が必要(約1週間の学習)
    • 最適化設定の理解に時間がかかる(公式ドキュメント約3時間)
    • トラブルシューティングの経験が必要(Stack Overflowで解決できない問題も)

実際のビルド結果を読み解く

ビルドログの詳細解説

 ✓ Compiled successfully        # TypeScript/JSXのコンパイル完了
 ✓ Linting and checking validity of types     # ESLintとTypeScriptの型チェック完了
 ✓ Collecting page data        # pages/もしくはapp/ディレクトリの解析
 ✓ Generating static pages (5/5)    # getStaticPropsの実行とHTMLの生成
 ✓ Collecting build traces     # 依存関係の追跡情報収集
 ✓ Finalizing page optimization    # 画像圧縮やCSSの最適化

これらのチェックマークは、ビルドプロセスの各段階が正常に完了したことを示しています。

要注意!ビルドサイズの分析

Route (app)                              Size     First Load JS
┌ ○ /                                    5.57 kB         111 kB
└ ○ /_not-found                          979 B           106 kB

First Load JSが100kBを超えている場合は要注意です!
以下の対策で20-30%の削減が可能です。

  • next/imageの使用で画像を最適化(JPG/PNGから自動でWebPに変換)
  • react-icons(約1.5MB)などの大きなライブラリをdynamic importで遅延ロード
    // 具体例
    import dynamic from 'next/dynamic'
    const FaIcon = dynamic(() => import('react-icons/fa').then(mod => mod.FaIcon))
    
  • Material UIの代わりにTailwind CSS(約40kB)を使用してバンドルサイズを削減

共有リソースの確認

+ First Load JS shared by all            105 kB
  ├ chunks/4bd1b696-20882bf820444624.js  52.9 kB
  ├ chunks/517-fe882a976a10ccc2.js       50.5 kB
  └ other shared chunks (total)          1.95 kB

全ページで共有されるJavaScriptファイルの内訳です。

トラブルシューティング

よくある問題と解決方法

  1. ビルドが遅い場合

    • 不要なパッケージの削除
    • キャッシュの活用
    • ビルド設定の最適化
  2. メモリ不足エラー

    # Node.jsのメモリ制限を緩和
    NODE_OPTIONS="--max-old-space-size=4096" npm run build
    
  3. 依存関係の問題

    # node_modulesを削除して再インストール
    rm -rf node_modules
    npm install
    

まとめ

ビルド結果を定期的にチェックすることで、以下のような具体的な改善が可能です。

パフォーマンスの予測

  • Lighthouseスコアが90点以上になるかを事前に確認
  • First Load JSが100kB以下になるようコード分割
  • 画像の合計サイズを1ページあたり500kB以下に抑制

最適化状態の確認

  • @next/bundle-analyzerでチャンクサイズの肥大化をチェック
  • 未使用のimport文をeslint-plugin-unused-importsで検出
  • next/imageの使用漏れをeslint-plugin-nextでチェック

開発効率の向上

  • ビルド時間を3分以内に抑えてCI/CDを効率化
  • PRレビュー時にバンドルサイズの変更を必ずチェック
  • Vercelのデプロイプレビューで本番の動作を確認

毎日のPRマージ前にnpm run build:prodを実行し、上記の項目をチェックすることで、パフォーマンスの高いNext.jsアプリケーションを維持できます!

Discussion