エラーの発生状況想像してみてください。Node.jsでAPIを構築している開発の真っ最中、スキーマを少し修正して保存したとします。NodemonやNext.jsの開発サーバーは200ミリ秒足らずで再起動しますが、突然ターミナルに次のような赤いスタックトレースが表示されます。
OverwriteModelError: Cannot overwrite model once compiled.
これはロジックのバグではありません。Mongooseのモデル登録処理と、最近のツールが採用しているホットモジュールリプレースメント(HMR)との競合が原因です。プロセス全体を終了させずにファイルをリロードすると、Mongooseは既存のモデルを再定義しようとしていると判断してしまいます。
分析:なぜMongooseはエラーを出すのかMongooseは作成されたすべてのモデルを中央レジストリで管理しています。mongoose.model('User', UserSchema)を呼び出すと、Mongooseはそのスキーマをコンパイルして内部キャッシュに保存します。実行パスが2回目にその行に到達すると、Mongooseは「User」モデルを新しいスキーマで上書きしようとしていると見なします。
デフォルトでは、データの破損や予期しない挙動を防ぐために、Mongooseはこの操作をブロックします。標準的な本番環境ではこれは安全機能として働きますが、Serverless関数やローカル開発環境では実行コンテキストが頻繁に再利用されるため、この安全チェックが大きな障害となります。
解決策:条件付きエクスポート無条件にモデルを定義するのではなく、mongoose.modelsオブジェクト内にそのモデルが既に存在するかどうかを確認すべきです。このパターンにより、キャッシュされたバージョンを再利用するか、必要な場合にのみ新しいモデルを作成することを保証できます。
モデルファイル(例:models/User.js)を以下のロジックで更新してください。
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
});
// 修正:コンパイル前にレジストリを確認する
const User = mongoose.models.User || mongoose.model('User', UserSchema);
module.exports = User;
この行はエンジンに対して「Userモデルが既にキャッシュにある場合はそれを使用し、なければ今すぐコンパイルせよ」と指示しています。これは開発サーバーを安定させるための最も効果的な方法です。
Next.jsとServerless環境への対応Next.jsは開発中、APIルートを頻繁にリロードします。そのため、シングルトンパターンを使用しない限り、OverwriteModelErrorはほぼ避けられません。モデルの修正に加えて、リロードのたびに新しいデータベース接続が作成されるのを防ぐ必要もあります。そうしないと、MongoDB Atlasなどの無料枠データベースで100接続制限にすぐに達してしまいます。
これを管理するために、中央集中型の接続ユーティリティを使用します。
// lib/db.js
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI;
if (!MONGODB_URI) {
throw new Error('MONGODB_URI環境変数を定義してください');
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) return cached.conn;
if (!cached.promise) {
cached.promise = mongoose.connect(MONGODB_URI).then((m) => m);
}
cached.conn = await cached.promise;
return cached.conn;
}
export default dbConnect;
この接続ユーティリティと条件付きモデルエクスポートを組み合わせることで、堅牢なデータレイヤーを構築できます。これにより、接続リークとコンパイルエラーの両方を一度に防ぐことができます。
実際に上書きが必要な場合カスタムREPLや動的なテストスイートなど、意図的にモデルを上書きしたい稀なケースがあります。プロセスは稼働し続けているがスキーマが変更されたという状況では、手動でキャッシュをクリアできます。
if (mongoose.models.User) {
delete mongoose.models.User;
}
const User = mongoose.model('User', UserSchema);
この方法は控えめに使用してください。本番環境でmongoose.modelsから削除すると、アプリの他の部分が古いインスタンスを使い続けている場合に、メモリリークやスキーマの不一致を引き起こす可能性があります。
修正の確認エラーが解消されたことを確認するために、次の3つのステップを試してください。
- リロードを実行する: モデルファイルを保存します。ターミナルに赤い文字が表示されることなく、サーバーが静かに再起動するはずです。- レジストリを検査する: APIルートに
console.log(Object.keys(mongoose.models))を追加します。モデル名が正しくリストされていることが確認できるはずです。- クエリを実行する: 基本的なUser.findOne()を実行します。スキーマに関するエラーが出ずにデータが返ってくれば、キャッシュされたモデルは完璧に動作しています。赤いスタックトレースが消えれば、モダンな開発ワークフローに適した環境設定が完了したことになります。

