🔥 Hot Reloading in Next.js and Database Connections

When building full-stack applications with Next.js, one common challenge developers face is managing database connections.
Unlike traditional Node.js apps, Next.js uses hot reloading and a serverless-like execution model, which can create problems if you don’t handle connections properly.
This blog will walk you through:
What hot reloading really means
Why it’s different from Node.js + Express
How to handle it with Mongoose (2 ways)
How to handle it with Prisma
What
log: ["query"]
in Prisma actually doesA ready-to-use Next.js + Prisma boilerplate
🚀 Node.js vs Next.js: What Changed?
If you’ve worked with Node.js + Express, connecting to a database feels straightforward:
Start the server once.
Create a database connection once.
All incoming requests reuse that same connection.
Pretty clean, right?
But when you switch to Next.js (development mode), things feel different. You might see errors like:
Error: Too many connections
MongooseError: Cannot overwrite User model once compiled
Why does this happen? 🤔 Let’s break it down.
🔄 What is Hot Reloading in Next.js?
In a regular Node.js + Express app:
The server runs continuously.
DB connection is opened once and reused.
In Next.js (dev mode):
Every time you save a file, hot reloading restarts the server code.
Each restart may try to create a new DB connection.
👉 The real problem is not that each request opens a connection — it’s that hot reload keeps re-creating them.
This leads to:
Too many connections (Prisma/MySQL/Postgres).
Cannot overwrite model once compiled (Mongoose/MongoDB).
🍽️ Real-Life Analogy: The Restaurant
Think of it like running a restaurant:
Normal Node.js app → You hire one chef when the restaurant opens, and they cook all day.
Next.js dev mode with hot reload → Every time you tweak the recipe, you hire a new chef. Soon, the kitchen is full of chefs.
⚡ Fix → Keep the same chef, even if the recipe changes.
That’s exactly what global singletons for DB connections do.
🟢 Handling Hot Reloading with Mongoose
Mongoose is the go-to ODM for MongoDB, but without proper handling, hot reload will break it.
Here are two common solutions:
1. Global Connection with mongoose.connection
import mongoose from "mongoose";
const connectDB = async () => {
if (mongoose.connection.readyState >= 1) return;
return mongoose.connect(process.env.MONGO_URI as string);
};
export default connectDB;
Or using a global variable:
import mongoose from "mongoose";
if (!(global as any)._mongooseConnection) {
(global as any)._mongooseConnection = mongoose.connect(process.env.MONGO_URI);
}
export default (global as any)._mongooseConnection;
✅ Ensures that if a connection already exists, we reuse it.
2. Global Model Caching
Hot reloading can also cause duplicate model compilation errors.
import mongoose, { Schema, model, models } from "mongoose";
const UserSchema = new Schema({
username: String,
email: String,
});
// Reuse existing model if already compiled
const User = models.User || model("User", UserSchema);
export default User;
✅ Prevents "Cannot overwrite model once compiled"
errors.
🔵 Handling Hot Reloading with Prisma
Prisma doesn’t use models like Mongoose. Instead, it generates a Prisma Client for database access.
But in dev mode, multiple clients can get created.
Solution → Use a singleton pattern with global:
import { PrismaClient } from "@prisma/client";
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: ["query"], // log SQL queries
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
✅ This ensures Prisma Client is created only once and reused.
📝 What Does log: ["query"]
Mean in Prisma?
Prisma allows you to configure logging:
"query"
→ Logs every SQL query."info"
→ General info."warn"
→ Warnings."error"
→ Errors.
Example:
prisma:query SELECT * FROM "User" WHERE "email" = $1
👉 Super useful in development to debug queries.
⚡ Ready-to-Use Prisma Boilerplate for Next.js
// lib/prisma.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: ["query"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
Usage in API routes or server actions:
import { prisma } from "@/lib/prisma";
export async function GET() {
const users = await prisma.user.findMany();
return Response.json(users);
}
📌 Best Practices
Centralize DB setup → put it in
lib/db.ts
orlib/prisma.ts
.Don’t connect inside API routes → always import your DB utility.
Use
.env
for database URLs (never hardcode).Enable logging (
log: ["query"]
) in dev mode.
🎯 Takeaways
Hot reloading in Next.js can cause multiple DB connections.
Mongoose Fix → Use
mongoose.connection
or model caching.Prisma Fix → Use a global singleton client.
log: ["query"]
helps debug Prisma queries.
✅ With these strategies, your Next.js app will be stable, efficient, and production-ready.
👉 Next time you see:
"Too many connections"
"Cannot overwrite model once compiled"
Remember: It’s not about requests — it’s about hot reload recreating your connections.
Subscribe to my newsletter
Read articles from Khushi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
