T-CREATOR

Next.js を Bun で動かす開発環境:起動速度・互換性・落とし穴

Next.js を Bun で動かす開発環境:起動速度・互換性・落とし穴

Next.js の開発をしていて「起動が遅い」「依存パッケージのインストールに時間がかかる」と感じたことはありませんか?

最近注目を集めている Bun を Next.js プロジェクトに導入すると、起動速度が劇的に向上し、開発体験が大きく改善されます。しかし、互換性の問題や予期せぬエラーに遭遇することもあるでしょう。

本記事では、Bun を使って Next.js を動かす開発環境の構築方法、実際の起動速度の違い、そして実運用で遭遇しがちな落とし穴とその対処法まで、実践的な内容をお届けします。

背景

Bun とは

Bun は、Zig 言語で開発された オールインワン JavaScript ランタイム です。Node.js や Deno と同様に JavaScript/TypeScript コードを実行できますが、圧倒的な速度を誇ります。

Bun には以下の機能が統合されています。

#機能説明
1ランタイムJavaScript/TypeScript の実行環境
2パッケージマネージャーnpm/yarn の代替(高速インストール)
3バンドラーwebpack/Vite などの代替
4テストランナーJest などの代替
5トランスパイラーTypeScript を直接実行可能

これらの機能が 単一のバイナリ に統合されているため、セットアップが簡単で、ツール間の連携もスムーズです。

なぜ Next.js で Bun を使うのか

Next.js の開発環境では、以下のような作業が頻繁に発生します。

  • 開発サーバーの起動(yarn dev
  • 依存パッケージのインストール(yarn install
  • ビルド処理(yarn build
  • テストの実行

これらの処理を Bun で実行すると、従来の Node.js + Yarn 環境と比較して 2〜5 倍の速度向上 が期待できるのです。

下図は、Bun を使った Next.js 開発フローの全体像を示しています。

mermaidflowchart TB
    dev["開発者"] -->|コマンド実行| bun["Bun<br/>ランタイム"]
    bun -->|"bun install"| pkg["パッケージ<br/>インストール"]
    bun -->|"bun dev"| next["Next.js<br/>開発サーバー"]
    bun -->|"bun run build"| build["本番ビルド"]

    pkg -->|高速化| cache["Bunキャッシュ"]
    next -->|HMR| browser["ブラウザ"]
    build -->|出力| dist[".next/"]

    style bun fill:#fbf0df
    style cache fill:#d4f1f4
    style next fill:#e8f5e9

Bun を中心としたワークフローでは、各処理が従来よりも高速に動作します。

Bun の速度の秘密

Bun が高速である理由は、以下の技術的な工夫にあります。

#技術効果
1JavaScriptCore エンジンV8(Node.js)より起動が高速
2Zig 言語での実装メモリ効率とパフォーマンスの最適化
3ネイティブコードへのコンパイル実行時のオーバーヘッド削減
4効率的なキャッシング重複ダウンロードの回避

特に、JavaScriptCore エンジンは Safari で使用されているエンジンで、起動速度に優れています。

課題

従来の Node.js + Yarn 環境の問題点

Next.js を Node.js と Yarn で開発する際、以下のような課題に直面することがあります。

パッケージインストールの遅さ

大規模なプロジェクトでは、依存パッケージが数百〜数千に及ぶことがあります。

typescript// package.json の依存関係の例
{
  "dependencies": {
    "next": "^14.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    // 数百の依存関係...
  }
}

これらすべてをインストールする場合、Yarn では 30 秒〜2 分 程度かかることも珍しくありません。

開発サーバーの起動速度

Next.js の開発サーバーを起動する際、以下の処理が実行されます。

  1. Node.js の起動
  2. Next.js の初期化
  3. ページのコンパイル
  4. Hot Module Replacement(HMR)の準備

特に初回起動時は、TypeScript のコンパイルも含めて 10〜20 秒 かかることがあります。

下図は、従来の開発フローにおける時間的なボトルネックを示しています。

mermaidflowchart LR
    start["yarn dev<br/>実行"] -->|5-10秒| node["Node.js<br/>起動"]
    node -->|5-10秒| init["Next.js<br/>初期化"]
    init -->|3-5秒| compile["ページ<br/>コンパイル"]
    compile -->|完了| ready["開発サーバー<br/>起動完了"]

    style start fill:#ffcdd2
    style node fill:#fff9c4
    style init fill:#fff9c4
    style compile fill:#fff9c4
    style ready fill:#c8e6c9

各ステップでの待ち時間が積み重なり、開発のテンポを損ねてしまいます。

メモリ消費の問題

Node.js は V8 エンジンのガベージコレクションにより、メモリ消費が大きくなりがちです。複数のプロジェクトを同時に開発している場合、RAM が不足することもあるでしょう。

ツールチェインの複雑さ

Next.js の開発環境では、以下のような複数のツールを組み合わせる必要があります。

#ツール用途
1Node.jsランタイム
2Yarn/npmパッケージマネージャー
3TypeScript型チェック
4ESLintコード品質チェック
5Jestテスト
6webpack/Turbopackバンドラー

これらのツールはそれぞれ設定ファイルを持ち、バージョン管理やアップデートの手間がかかります。Bun であれば、これらの機能の多くが統合されているため、設定がシンプルになるのです。

解決策

Bun のインストール

まず、Bun をシステムにインストールしましょう。以下のコマンドで最新版がインストールされます。

bash# macOS / Linux の場合
curl -fsSL https://bun.sh/install | bash
bash# Windows の場合(PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"

インストールが完了したら、バージョンを確認します。

bash# Bun のバージョン確認
bun --version

正常にインストールされていれば、バージョン番号(例:1.0.0)が表示されます。

新規 Next.js プロジェクトの作成

Bun を使って新しい Next.js プロジェクトを作成する場合、以下のコマンドを使用します。

bash# Bun で Next.js プロジェクトを作成
bun create next-app my-nextjs-app

このコマンドは、内部的に create-next-app を実行しますが、Bun のパッケージマネージャーが使用されるため、従来の npm より 2〜3 倍高速 です。

プロジェクト作成時の選択肢は以下の通りです。

bash✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? … Yes
✔ Would you like to customize the default import alias? … No

これらの設定は、プロジェクトの要件に応じて選択してください。

既存プロジェクトへの導入

すでに存在する Next.js プロジェクトに Bun を導入する場合は、以下の手順を実施します。

package.json の確認

まず、現在の package.json を確認しましょう。

json{
  "name": "my-nextjs-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

scripts セクションのコマンドは、そのまま Bun で実行できます。

依存パッケージのインストール

既存の node_modules を削除し、Bun で再インストールします。

bash# 既存の node_modules とロックファイルを削除
rm -rf node_modules package-lock.json yarn.lock
bash# Bun でパッケージをインストール
bun install

このコマンドにより、bun.lockb という バイナリ形式のロックファイル が生成されます。このファイルは Git で管理することをお勧めします。

開発サーバーの起動

Bun を使って開発サーバーを起動するには、以下のコマンドを実行します。

bash# Bun で Next.js 開発サーバーを起動
bun dev

従来の yarn dev と同じ動作をしますが、起動速度が大幅に向上します。

下図は、Bun を導入した後の開発フローを示しています。

mermaidflowchart LR
    start["bun dev<br/>実行"] -->|1-2秒| bun_start["Bun<br/>起動"]
    bun_start -->|2-3秒| next_init["Next.js<br/>初期化"]
    next_init -->|1-2秒| compile["ページ<br/>コンパイル"]
    compile -->|完了| ready["開発サーバー<br/>起動完了"]

    style start fill:#e1f5fe
    style bun_start fill:#b2ebf2
    style next_init fill:#b2ebf2
    style compile fill:#b2ebf2
    style ready fill:#c8e6c9

各ステップの処理時間が短縮され、トータルで 5〜8 秒程度 で起動できます。

具体例

起動速度の比較

実際のプロジェクトで、Yarn と Bun の起動速度を比較してみましょう。

測定環境

#項目内容
1OSmacOS Sonoma 14.0
2CPUApple M2 Pro
3RAM16GB
4Next.js14.0.0
5依存パッケージ数約 300

パッケージインストールの比較

まず、依存パッケージのインストール速度を比較します。

bash# Yarn でのインストール(時間計測)
time yarn install

Yarn での結果は以下の通りです。

bash# Yarn の実行結果
yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 45.32s.

45 秒 かかりました。

次に、Bun で同じ操作を実行します。

bash# Bun でのインストール(時間計測)
time bun install

Bun での結果は以下の通りです。

bash# Bun の実行結果
bun install v1.0.0
Resolving packages...
Downloading packages...
Installing packages...
Done in 12.84s.

13 秒 で完了しました。これは Yarn の 約 3.5 倍の速度 です。

下図は、インストール時間の比較を視覚化したものです。

mermaidflowchart TB
    subgraph yarn_flow["Yarn(45.32秒)"]
        y1["Resolving<br/>10秒"]
        y2["Fetching<br/>25秒"]
        y3["Linking<br/>8秒"]
        y4["Building<br/>2秒"]
        y1 --> y2 --> y3 --> y4
    end

    subgraph bun_flow["Bun(12.84秒)"]
        b1["Resolving<br/>3秒"]
        b2["Downloading<br/>8秒"]
        b3["Installing<br/>2秒"]
        b1 --> b2 --> b3
    end

    style yarn_flow fill:#ffebee
    style bun_flow fill:#e8f5e9

Bun は各フェーズが並列化されており、全体的に高速です。

開発サーバー起動の比較

次に、開発サーバーの起動速度を比較します。

bash# Yarn での起動(時間計測)
time yarn dev

Yarn での起動時間は以下の通りです。

bash# Yarn の実行結果
▲ Next.js 14.0.0
- Local:        http://localhost:3000
✓ Ready in 18.2s

18 秒 で起動しました。

同じプロジェクトを Bun で起動します。

bash# Bun での起動(時間計測)
time bun dev

Bun での起動時間は以下の通りです。

bash# Bun の実行結果
▲ Next.js 14.0.0
- Local:        http://localhost:3000
✓ Ready in 6.7s

7 秒 で起動しました。これは Yarn の 約 2.7 倍の速度 です。

実践的な設定例

bunfig.toml の作成

Bun の動作をカスタマイズするために、プロジェクトルートに bunfig.toml を作成します。

toml# bunfig.toml
# Bun の設定ファイル

[install]
# パッケージインストールの設定
cache = true  # キャッシュを有効化
optional = true  # オプショナル依存関係をインストール
dev = true  # devDependencies もインストール

この設定により、Bun のパッケージマネージャーが最適化されます。

toml[install.scopes]
# プライベートレジストリの設定(必要な場合)
# "@mycompany" = { token = "YOUR_TOKEN", url = "https://npm.mycompany.com" }

社内のプライベートパッケージを使用する場合は、この設定を追加しましょう。

TypeScript の設定

Bun は TypeScript をネイティブサポートしていますが、Next.js との互換性を確保するために tsconfig.json を調整します。

json{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "jsx": "preserve",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true
  }
}

moduleResolution"bundler" に設定することで、Bun のモジュール解決アルゴリズムと一致します。

json{
  "compilerOptions": {
    // ...前述の設定
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

パスエイリアスも正常に動作します。

package.json のスクリプト最適化

Bun を活用するために、package.json のスクリプトを最適化しましょう。

json{
  "scripts": {
    "dev": "bun --bun next dev",
    "build": "bun --bun next build",
    "start": "bun --bun next start",
    "lint": "next lint",
    "test": "bun test"
  }
}

--bun フラグを付けることで、Bun のランタイムを 強制的に使用 します。これにより、Node.js 互換モードではなく、完全に Bun のエンジンで実行されます。

互換性の確認とテスト

Next.js のすべての機能が Bun で正常に動作するか、テストを実施します。

API Routes のテスト

まず、API Routes が正常に動作するか確認しましょう。

typescript// src/app/api/hello/route.ts
// シンプルな API エンドポイント

import { NextResponse } from 'next/server';

export async function GET() {
  // JSON レスポンスを返す
  return NextResponse.json({
    message: 'Hello from Bun + Next.js!',
    timestamp: new Date().toISOString(),
  });
}

このエンドポイントを Bun で起動したサーバーでテストします。

bash# 開発サーバーを起動
bun dev
bash# 別のターミナルで API をテスト
curl http://localhost:3000/api/hello

レスポンスが正常に返ってくれば、API Routes は互換性があります。

json{
  "message": "Hello from Bun + Next.js!",
  "timestamp": "2025-11-21T10:30:00.000Z"
}

サーバーコンポーネントのテスト

Next.js 14 の App Router を使用している場合、Server Components の動作も確認します。

typescript// src/app/page.tsx
// サーバーコンポーネントでデータフェッチ

async function getData() {
  // 外部 API からデータを取得
  const res = await fetch('https://api.example.com/data', {
    cache: 'no-store', // 常に最新データを取得
  });

  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }

  return res.json();
}

データフェッチ関数を定義した後、コンポーネントで使用します。

typescriptexport default async function Home() {
  // サーバー側でデータを取得
  const data = await getData();

  return (
    <main>
      <h1>Server Component with Bun</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </main>
  );
}

ページをブラウザで開き、データが正常に表示されれば OK です。

落とし穴と対処法

よくある互換性の問題

Bun は Node.js との互換性を目指していますが、完全ではありません。以下のような問題に遭遇する可能性があります。

ネイティブモジュールの問題

一部の npm パッケージは、C/C++ で書かれた ネイティブアドオン を使用しています。これらは Bun では動作しないことがあります。

bash# エラー例
error: Cannot find native module "sharp"
  at node_modules/sharp/lib/index.js:10:15

このエラーは、sharp という画像処理ライブラリが Node.js のネイティブバインディングを使用しているために発生します。

対処法:Bun 互換の代替パッケージを使用するか、Node.js モードで実行します。

bash# Node.js 互換モードで実行(--bun フラグを外す)
bun run next dev

この場合、Bun は内部的に Node.js との互換レイヤーを使用します。

process.env の違い

Bun と Node.js では、環境変数の扱いに若干の違いがあります。

typescript// .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=your-secret-key

Next.js では、NEXT_PUBLIC_ プレフィックスのある変数のみがクライアント側で利用可能です。これは Bun でも同様に動作しますが、注意が必要です。

typescript// src/lib/config.ts
// 環境変数を安全に取得

export const config = {
  // クライアント側で使用可能
  apiUrl:
    process.env.NEXT_PUBLIC_API_URL ||
    'http://localhost:3000',

  // サーバー側のみで使用可能
  secretKey: process.env.SECRET_KEY,
};

環境変数が undefined にならないよう、デフォルト値を設定しましょう。

typescript// 型安全な環境変数の取得
if (!process.env.SECRET_KEY) {
  throw new Error(
    'SECRET_KEY is not defined in environment variables'
  );
}

必須の環境変数はアプリケーション起動時にチェックすると安全です。

パフォーマンスの問題

メモリリークの検出

Bun は高速ですが、適切に使わないとメモリリークが発生することがあります。

typescript// src/lib/cache.ts
// メモリリークの原因となる例

const cache = new Map(); // グローバルなキャッシュ

export function cacheData(key: string, value: any) {
  // キャッシュが無限に増え続ける可能性がある
  cache.set(key, value);
}

この実装では、キャッシュのサイズ制限がないため、メモリリークの原因になります。

対処法:LRU(Least Recently Used)キャッシュを実装します。

typescript// 改善版:サイズ制限付きキャッシュ
const MAX_CACHE_SIZE = 100;
const cache = new Map();

export function cacheData(key: string, value: any) {
  // キャッシュサイズが上限を超えたら古いエントリを削除
  if (cache.size >= MAX_CACHE_SIZE) {
    const firstKey = cache.keys().next().value;
    cache.delete(firstKey);
  }

  cache.set(key, value);
}

この実装により、キャッシュのサイズが制限され、メモリ使用量が安定します。

ホットリロードの不具合

Bun で Next.js を動かすと、まれに Hot Module Replacement(HMR)が正常に動作しないことがあります。

症状:ファイルを編集しても、ブラウザが自動的に更新されない。

bash# エラーログ(例)
Error: HMR connection lost
  at WebSocket.onclose

このエラーは、WebSocket 接続が切断されたときに発生します。

対処法 1:開発サーバーを再起動します。

bash# Ctrl+C でサーバーを停止
# 再度起動
bun dev

対処法 2next.config.js で HMR の設定を調整します。

javascript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // HMR のポーリング間隔を設定(ミリ秒)
  webpack: (config, { dev }) => {
    if (dev) {
      config.watchOptions = {
        poll: 1000, // 1秒ごとにファイル変更をチェック
        aggregateTimeout: 300, // 変更検知後300ms待ってからリビルド
      };
    }
    return config;
  },
};

module.exports = nextConfig;

この設定により、ファイル変更の検知がより確実になります。

デプロイ時の注意点

Vercel でのデプロイ

Vercel は Next.js の公式ホスティングサービスですが、Bun を使用している場合は注意が必要です。

重要:Vercel のビルド環境は Node.js を使用するため、package.json が Node.js と互換性がある必要があります。

json{
  "engines": {
    "node": ">=18.0.0"
  },
  "scripts": {
    "dev": "bun dev",
    "build": "next build",
    "start": "next start"
  }
}

build スクリプトは bun を使わず、next build のみにすることで、Vercel との互換性を保ちます。

Docker でのデプロイ

Docker コンテナで Bun を使用する場合、以下のような Dockerfile を作成します。

dockerfile# Dockerfile
# Bun を使った Next.js のコンテナイメージ

FROM oven/bun:1.0 as base

# 作業ディレクトリを設定
WORKDIR /app

ベースイメージには公式の oven​/​bun イメージを使用します。

dockerfile# 依存関係のインストール
FROM base as deps

# package.json と bun.lockb をコピー
COPY package.json bun.lockb ./

# Bun でパッケージをインストール
RUN bun install --frozen-lockfile

--frozen-lockfile フラグにより、ロックファイルと完全に一致するバージョンがインストールされます。

dockerfile# ビルド
FROM base as builder

# 依存関係とソースコードをコピー
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js をビルド
RUN bun run build

ビルドステージでは、アプリケーションをプロダクション用にビルドします。

dockerfile# 本番環境
FROM base as runner

# 本番用の環境変数を設定
ENV NODE_ENV=production

# ビルド成果物をコピー
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

# ポートを公開
EXPOSE 3000

# アプリケーションを起動
CMD ["bun", "start"]

この Dockerfile により、最適化された Docker イメージが作成されます。

bash# Docker イメージのビルド
docker build -t my-nextjs-app .
bash# コンテナの起動
docker run -p 3000:3000 my-nextjs-app

環境変数の管理

本番環境では、環境変数を安全に管理する必要があります。

bash# .env.production(本番用の環境変数)
NEXT_PUBLIC_API_URL=https://api.production.com
DATABASE_URL=postgresql://user:password@host:5432/db
SECRET_KEY=production-secret-key

重要.env.production は Git にコミットせず、.gitignore に追加してください。

bash# .gitignore に追加
.env*.local
.env.production

デプロイ先の環境(Vercel、AWS、Docker など)で環境変数を設定しましょう。

トラブルシューティング

Error: Cannot find module

Bun でモジュールが見つからないエラーが発生した場合の対処法です。

bash# エラー例
Error: Cannot find module "@/lib/utils"
  at /app/src/app/page.tsx:1:0

このエラーは、パスエイリアスが正しく解決されていないときに発生します。

対処法tsconfig.json のパス設定を確認します。

json{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

また、bunfig.toml でもパス解決を設定できます。

toml[module]
# モジュール解決の設定
aliases = { "@" = "./src" }

Error: fetch failed

API 呼び出しが失敗する場合、以下を確認してください。

bash# エラー例
Error: fetch failed
  at async getData (src/app/page.tsx:5:15)

このエラーは、ネットワークの問題や CORS 設定の問題で発生します。

対処法 1:環境変数の URL が正しいか確認します。

typescriptconsole.log('API URL:', process.env.NEXT_PUBLIC_API_URL);

対処法 2:fetch にタイムアウトを設定します。

typescriptasync function getData() {
  const controller = new AbortController();
  const timeoutId = setTimeout(
    () => controller.abort(),
    5000
  ); // 5秒でタイムアウト

  try {
    const res = await fetch(
      'https://api.example.com/data',
      {
        signal: controller.signal,
      }
    );
    clearTimeout(timeoutId);
    return res.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('Request timeout after 5 seconds');
    }
    throw error;
  }
}

タイムアウトを設定することで、長時間のハングを防げます。

まとめ

Bun を使った Next.js 開発環境は、以下のような大きなメリットがあります。

#メリット効果
1起動速度の向上開発サーバーが 2〜3 倍高速に起動
2パッケージインストールの高速化従来の 3〜5 倍の速度でインストール完了
3メモリ効率の改善V8 より軽量な JavaScriptCore を採用
4ツールの統合ランタイム・パッケージマネージャー・テストランナーが一体化
5TypeScript ネイティブサポートトランスパイル不要で TypeScript を実行

一方で、以下のような落とし穴にも注意が必要です。

#落とし穴対処法
1ネイティブモジュールの非互換性Node.js 互換モードで実行または代替パッケージを使用
2HMR の不具合webpack の watchOptions を調整
3デプロイ環境の制約Vercel では build スクリプトから bun を外す
4環境変数の扱い必須変数の存在チェックを実装

Bun はまだ比較的新しいツールですが、Next.js との組み合わせで 開発体験を大きく向上 させることができます。小さなプロジェクトから試してみて、チームの開発フローに合うか検証してみてはいかがでしょうか。

特に、頻繁にパッケージを追加したり、開発サーバーを再起動したりする場面では、Bun の速度の恩恵を実感できるはずです。ぜひ、実際のプロジェクトで Bun を試してみてください。

関連リンク