感情王国
ブログ一覧に戻る

Next.js開発サーバー終了後もポートが解放されない問題の解決方法

Next.jsで開発していると、開発サーバーをCtrl+Cで終了した後、再度起動しようとすると以下のようなエラーに遭遇することがあります。

Error: listen EADDRINUSE: address already in use :::3000

特に開発中に頻繁にサーバーの起動・停止を繰り返していると、このエラーが発生しやすくなります。この記事では、この問題の原因と、自動化された解決策を紹介します。

問題の詳細

どんな状況で発生するか

  • Ctrl+CでNext.js開発サーバーを停止した後
  • ターミナルを強制終了した後
  • 開発サーバーがエラーでクラッシュした後
  • 複数回起動・停止を繰り返した後

これらの状況で、ポート3000(デフォルトポート)が解放されず、次回の起動時にエラーが発生します。

実際の影響

毎回以下のような手作業が必要になります:

# ポートを使用しているプロセスを確認
lsof -ti:3000

# プロセスをkill
lsof -ti:3000 | xargs kill -9

# 再度開発サーバーを起動
yarn dev

これは非常に煩わしく、開発フローを妨げます。

問題の原因

1. Node.jsプロセスの終了処理

Ctrl+Cを押すと、Node.jsプロセスにSIGINTシグナルが送信されます。通常、アプリケーションはこのシグナルを受け取り、クリーンアップ処理(ポートの解放など)を行ってから終了します。

しかし、以下の場合にクリーンアップが不完全になることがあります:

  • プロセスが異常終了した場合
  • シグナル処理が正しく実装されていない場合
  • 複数のプロセスが起動している場合
  • OSのプロセス管理の遅延

2. macOSのプロセス管理の特性

macOSでは、プロセス終了時のリソース解放にわずかな遅延が発生することがあります。特に以下の条件下で顕著です:

  • システムリソースが逼迫している
  • 複数のNode.jsプロセスが動作している
  • ファイルシステムのI/O待ちが発生している

3. Next.jsの開発サーバーの仕組み

Next.jsの開発サーバーは、以下のようなプロセスを内部で管理しています:

  • メインのHTTPサーバープロセス
  • ホットリロード用のWebSocketサーバー
  • ファイル監視プロセス
  • コンパイラプロセス

これらのプロセスが全て正常に終了しないと、ポートが解放されません。

従来の解決方法とその問題点

方法1: 手動でプロセスをkill

lsof -ti:3000 | xargs kill -9

問題点:

  • 毎回コマンドを打つ必要がある
  • タイプミスのリスク
  • 他のチームメンバーも同じ手順を覚える必要がある
  • 開発フローが中断される

方法2: ポート番号を変更

# package.jsonで
"dev": "next dev -p 3001"

問題点:

  • 根本的な解決にならない
  • 他のツールやサービスとの連携で問題が発生
  • ドキュメントやURLを変更する必要がある
  • 同じ問題が別のポートで再発する可能性

方法3: OSを再起動

問題点:

  • 言うまでもなく非現実的
  • 開発効率が著しく低下

自動化された解決策

この問題を根本的に解決するために、開発サーバー起動前に自動でポートをクリーンアップする方法を導入しました。

使用するツール

npm-run-all2: 複数のnpmスクリプトを順次または並列で実行できるツールです。

yarn add -D npm-run-all2

package.jsonの設定

{
  "scripts": {
    "dev": "run-s kill-port next-dev",
    "next-dev": "next dev",
    "kill-port": "lsof -ti:3000 | xargs kill -9"
  }
}

仕組みの解説

  1. yarn devを実行
  2. run-s(run-sequentially)により、順次実行が開始
  3. まずkill-portスクリプトが実行され、ポート3000を使用しているプロセスを強制終了
  4. その後、next-devスクリプトが実行され、Next.js開発サーバーが起動

run-sは各コマンドを順番に実行し、前のコマンドが完了するまで次のコマンドを実行しません。これにより、ポートのクリーンアップが確実に完了してから開発サーバーが起動します。

lsofコマンドの詳細

lsof -ti:3000 | xargs kill -9
  • lsof: "list open files"の略。オープンしているファイルやネットワーク接続を表示
  • -t: プロセスIDのみを出力
  • -i:3000: ポート3000を使用している接続を検索
  • xargs kill -9: パイプで受け取ったプロセスIDに対してkill -9を実行

kill -9SIGKILLシグナルを送信し、プロセスを強制終了します。これはクリーンアップ処理を待たずに即座にプロセスを終了させます。

実装手順

1. npm-run-all2のインストール

yarn add -D npm-run-all2

2. package.jsonの編集

package.jsonscriptsセクションを以下のように変更:

{
  "scripts": {
    "dev": "run-s kill-port next-dev",
    "next-dev": "next dev",
    "kill-port": "lsof -ti:3000 | xargs kill -9"
  }
}

3. 動作確認

# 普通に起動
yarn dev

# ポートが残っている状態でも自動的にクリーンアップされて起動
yarn dev

この解決策のメリット

1. 完全な自動化

開発者は何も気にせずyarn devを実行するだけで、常にクリーンな状態で開発サーバーが起動します。

2. チーム全体で共有可能

package.jsonに設定を記述することで、チーム全体で同じ開発体験を共有できます。新しいメンバーも特別な手順を覚える必要がありません。

3. 開発効率の向上

手動でのプロセスkillが不要になり、スムーズな開発フローを維持できます。

4. エラーへの耐性

どんな状況(クラッシュ、強制終了、複数回の起動など)でも、確実に起動できます。

注意点と考慮事項

1. kill -9の影響

kill -9は強制終了なので、以下の影響があります:

  • プロセスのクリーンアップ処理がスキップされる
  • データの保存が不完全になる可能性(開発環境なので通常は問題なし)
  • ファイルロックが残る可能性(稀)

開発環境での使用を前提としているため、これらのリスクは許容範囲内です。

2. 他のアプリケーションがポート3000を使用している場合

もし他のアプリケーションがポート3000を使用している場合、そのプロセスも終了してしまいます。これを避けるには:

  • Next.jsのデフォルトポートを変更する
  • より精密なプロセス識別を行う
# プロセス名も確認する例
lsof -ti:3000 -sTCP:LISTEN | xargs ps -p | grep node | awk '{print $1}' | xargs kill -9

3. Windows環境での代替方法

macOS/Linux用のlsofコマンドはWindowsでは使用できません。Windows環境では以下の代替方法を使用します:

{
  "scripts": {
    "dev": "run-s kill-port next-dev",
    "next-dev": "next dev",
    "kill-port": "netstat -ano | findstr :3000 | findstr LISTENING"
  }
}

または、クロスプラットフォーム対応のkill-portパッケージを使用:

yarn add -D kill-port
{
  "scripts": {
    "dev": "run-s kill-port next-dev",
    "next-dev": "next dev",
    "kill-port": "kill-port 3000"
  }
}

まとめ

Next.js開発サーバーのポート解放問題は、npm-run-all2を使った自動化で簡単に解決できます。

この記事のポイント:

  1. ポートが解放されない問題は、プロセスの異常終了やOSのリソース管理が原因
  2. npm-run-all2lsofコマンドを組み合わせることで、起動前の自動クリーンアップが可能
  3. package.jsonへの設定により、チーム全体で問題を解決
  4. 小さな自動化が開発体験を大きく向上させる

開発環境の小さなストレスを取り除くことは、長期的に見て生産性の向上につながります。同じような問題に直面している方は、ぜひこの方法を試してみてください。

参考リンク