A Server Component runs in Node.js 2,131 , so it can skip HTTP and talk to MongoDB 1,815 through the Mongoose 243,355 models of Mongoose; nothing in the query reaches the browser. What you must get right is the connection. next dev reloads modules on every edit, so a naive mongoose.connect() at module level opens a new pool per reload and eventually hits too many connections. Cache the connection promise on globalThis, which survives the reload, and guard the model registration — a second mongoose.model("Book", ...) throws OverwriteModelError.
import "server-only";
import mongoose from "mongoose";
import { cache } from "react";
const store = (globalThis._mongoose ??= { conn: null, promise: null });
export async function connect() {
if (store.conn) return store.conn;
store.promise ??= mongoose.connect(process.env.MONGODB_URI,
{ bufferCommands: false, maxPoolSize: 10 });
return (store.conn = await store.promise);
}
export const Book = mongoose.models.Book ?? mongoose.model("Book", bookSchema);
export const booksByGenre = cache(async (genre) => { // one query per request
await connect();
return Book.find({ genre }).sort({ year: 1 }).lean();
});Two components on one page both call booksByGenre("sci-fi"). With mongoose.set("debug", true), Mongoose 9.10.1 shows what reached MongoDB 8.2.6 — one connection, one query:
[db] opening a new connection
Mongoose: books.find({ genre: 'sci-fi' }, { sort: { year: 1 } })
GET /shelf 200 in 949ms (next.js: 287ms, application-code: 662ms)
Mongoose: books.find({ genre: 'sci-fi' }, { sort: { year: 1 } }) <- second request
GET /shelf 200 in 42ms (next.js: 3ms, application-code: 39ms)The second request never reopens the pool, and 949 ms becomes 42 ms. Without cache() the second component would have issued its own find: Mongoose queries are not memoized the way fetch is.
Three details matter as much as the connection. .lean() returns plain objects instead of Mongoose documents: faster, and also necessary, since a hydrated document is not serializable across the client boundary (Serializing Props) and _id is an ObjectId you must stringify yourself. bufferCommands: false makes a query fail fast when the database is unreachable instead of hanging until the request times out. And a Server Component has no idea who is asking, so every authorization rule your Express 24,430 routes enforce belongs here too.