cache - React Server Component
Recently I start paying with Next.js 15 Server Components and I realize I was making the same DB query multiple times in different components for the same page request.
I realized that I did something wrong here, do we really need to hit the database every time we need the same piece of data ? even within a single page request ?
I realized that I can reduce the call by only call it once on a top level component and props drilling it. but there are also part of my code where it is not possible. So I dig around and found cache.
The problem
Here’s a simplified example of a project structure:
board/page.tsx
board/layout.tsx
board/_components/board-nav.tsx
All of these files needed the board data:
board = await dbClient.board.findUnique({
where: {
id,
organization_id,
}
});
I’ve noticed that I also repeated the same query inside generateMetadata to render a dynamic page title.
**Two problems here: **
- Code duplication - the same query written multiple times
- Multiple DB calls ? - even if its within the same board web page.
Shared Function
My first step was to extract the query into a single resuable function:
// actions/fetchUserBoardById.ts
export const fetchUserBoardById = async (data: { id: string; organization_id: string }) => {
console.log("call me once?");
const { organization_id, id } = data;
try {
const board = await dbClient.board.findUnique({
where: { id, organization_id },
});
return { data: board };
} catch (error) {
return { error: "Failed to fetch" };
}
};
Now my code was cleaner, but when I ran it, the console still showed:
GET /boards/c4f9800d-ce96-425d-93fb-e3b41df94e3a 200 in 294ms
call me once?
call me once?
call me once?
on a single request to
GET /boards/c4f9800d-ce96-425d-93fb-e3b41df94e3a
but calling the db 3 times.
using Cache
React’s cache lets you memoize a function’s result for the duration of a single server render.
If the same function is called multiple times in the same request with the same parameters, it only runs once.
Here’s the fixed version:
import { cache } from "react";
export const fetchUserBoardById = cache(
async (id: string, organization_id: string) => {
console.log("call me once?"); // Logs only once per request for same params
try {
const board = await dbClient.board.findUnique({
where: { id, organization_id },
});
return { data: board };
} catch (error) {
return { error: "Failed to fetch" };
}
}
);
Result:
call me once? // logged once
Parameter Identity
cache() uses parameter identity as its key.
if you pass a new object each time, React treats it as a different key.
Event if it has the same content.
// ❌ These are different cache entries (different object references)
await fetchUserBoardById({ id: "123", organization_id: "org-1" });
await fetchUserBoardById({ id: "123", organization_id: "org-1" });
// ✅ These are the same cache entry (primitives)
await fetchUserBoardById("123", "org-1");
await fetchUserBoardById("123", "org-1");
Think of this like a dependency array in useEffect , a stable values are the keys.
Common Mistake: Wrapping After Definition
I’ve made a mistake when I first try to use cache where I wrap already defined function.
cache() should wrap your actual function directly, and should not be applied to an already defined handler like this:
// ❌ Wrong
export const handler = async (data) => { ... };
export const fetchUserBoardById = cache(handler);
// ✅ Right
export const fetchUserBoardById = cache(async (id, organization_id) => { ... });
Important Notes
- Per-request only — The cache resets between requests. It’s not like Redis or in-memory app cache.
- Next.js fetch() is special — When you use the built-in
fetch(), Next.js already deduplicates requests within the same render. But Prisma (or other DB clients) doesn’t, socache()is useful. - Not a replacement for persistent caching — If you need to avoid DB hits across requests, you still need Redis, a CDN, or another external cache.
When to use cache()
- Expensive DB calls reused across multiple Server Components during one render.
- Data needed in
generateMetadataand component trees without prop drilling. - Situations where you want to avoid passing data through many layers.
Final Tip:
Use cache() for server-only data fetching functions where you know multiple parts of your app might call them in the same render. It keeps your code clean, your DB happy, and your render faster.