🔥 Hot Reloading in Next.js and Database Connections

KhushiKhushi
4 min read

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 does

  • A 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:

  1. Start the server once.

  2. Create a database connection once.

  3. 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 or lib/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.


10
Subscribe to my newsletter

Read articles from Khushi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Khushi
Khushi