Next.js Automatically Dedupes fetch() Calls. Your MongoDB Queries Don't Get That for Free.

1 13
calendar_today agoschedule3 min read

Here's something that trips up almost everyone moving from a fetch-based API to a direct MongoDB connection in Next.js, and it's costing real database load on projects that don't even realize it's happening.

Next.js automatically deduplicates fetch() calls with identical parameters within a single render pass. If your layout, your page, and three nested components all call fetch('/api/user'), Next.js is smart enough to only actually make that request once and reuse the result everywhere else it's called. This is called request memoization, and it's one of the quieter, genuinely useful things the App Router does for you automatically.

Direct database queries get none of this. Nothing.

What This Actually Looks Like

// lib/queries/user.ts
import { connectDB } from '@/lib/db';
import User from '@/models/User';

export async function getCurrentUser(userId: string) {
  await connectDB();
  return User.findById(userId).lean();
}
// app/dashboard/layout.tsx
export default async function DashboardLayout({ children }) {
  const user = await getCurrentUser(userId); // query #1
  return <div><Sidebar user={user} />{children}</div>;
}

// app/dashboard/page.tsx
export default async function DashboardPage() {
  const user = await getCurrentUser(userId); // query #2, same user, same request
  return <Header user={user} />;
}

// components/UserMenu.tsx (Server Component, rendered inside the page)
export default async function UserMenu() {
  const user = await getCurrentUser(userId); // query #3
  return <Menu user={user} />;
}

Three separate calls to getCurrentUser, same user ID, same single incoming request from the browser. If this were fetch('/api/user') instead of a direct Mongoose query, Next.js would automatically collapse these into one actual network call. Since it's a direct database query, none of that automatic deduplication applies. This is three real round trips to MongoDB, for data that was identical all three times, on every single page load.

Why This Doesn't Show Up in Development

On a local database with almost no latency and no real load, three redundant queries feel instant, indistinguishable from one. This is exactly why it's so easy to ship without noticing. The cost only becomes visible under real production conditions, real network latency to your database, real concurrent traffic, where those extra round trips add up into measurably slower page loads and meaningfully higher database load than the page actually needed.

The Actual Fix: React's cache() Function

React ships a cache() function specifically for this. It memoizes a function's result within a single render pass, exactly the same behavior fetch gets automatically, just applied manually to whatever function you wrap with it.

// lib/queries/user.ts
import { cache } from 'react';
import { connectDB } from '@/lib/db';
import User from '@/models/User';

export const getCurrentUser = cache(async (userId: string) => {
  await connectDB();
  return User.findById(userId).lean();
});

That's the entire fix. Every call to getCurrentUser with the same userId, anywhere in that same render pass, layout, page, nested component, now returns the same cached promise instead of triggering a new query. Three calls become one actual database hit.

The Part People Get Wrong About cache()

This is request-scoped, not persistent across requests. It deduplicates calls within one single render of one single incoming request, then resets completely for the next request. It is not the same thing as unstable_cache, which persists data across multiple requests and needs explicit revalidation.

// cache() - deduplicates within ONE request, resets every time
export const getCurrentUser = cache(async (userId: string) => { ... });

// unstable_cache - persists ACROSS requests, needs revalidateTag to update
export const getPosts = unstable_cache(async () => { ... }, ['posts'], {
  revalidate: 3600,
});

Mixing these up causes two different problems. Using cache() where you needed unstable_cache means you're still hitting the database on every single request, just once instead of three times. Using unstable_cache where you needed cache() means you're persisting data across requests when you actually just wanted to avoid redundant calls within one page load, which can serve stale data longer than intended.

Where I'd Actually Add This

Not everywhere, that would be over-engineering a solution for calls that only ever happen once per request anyway. The pattern worth watching for specifically: any query function, getCurrentUser, getTenant, getSession-adjacent lookups, that reasonably gets called from multiple places in the same component tree, a layout, a page, and a few nested components all independently needing "who is the current user."

Check Your Own Project

Grep your query functions for ones called from more than one component in the same tree, and check whether they're wrapped in cache(). If they're not, and they're not already going through unstable_cache or an equivalent persistent layer, you likely have the exact redundant-query pattern above running silently in production right now.


Curious how many people actually knew about this before reading this post versus assumed Next.js handled it automatically the same way it does for fetch. Drop your honest answer in the comments, genuinely curious how well-known this actually is.

Get the templates: https://pixelanas.gumroad.com


Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751

2 Comments

1 vote
0
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

Complete MDX Blog Setup with next-mdx-remote and Next.js 16 (2026 Beginner Guide)

muhammadali - Jul 30

Merancang Backend Bisnis ISP: API Pelanggan, Paket Internet, Invoice, dan Tiket Support

Masbadar - Mar 13

Build a Full-Stack Video Streaming App with React.js, Node.js, Next.js, MongoDB, Bunny CDN, and Material UI

torver213 - Mar 29, 2025

5 Things This Playwright SQL Fixture Does So You Don't Have To

vitalicset - Apr 13
chevron_left
360 Points14 Badges
11Posts
2Comments
1Connections
Full-stack developer building premium Next.js templates & web apps. Specializing in GSAP animations,... Show more

Related Jobs

View all jobs →

Commenters (This Week)

3 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!