Next.js automatically deduplicates fetch requests, and React provides a cache function that deduplicates any async work you wrap. They produce the same visible result but cover different ground: fetch deduplication is automatic and narrow, while React cache is manual and general.
| fetch deduplication | React cache() | |
|---|---|---|
| Automatic | Yes | No, you wrap the function |
| Covers | fetch GET with the same URL and options | Any async function |
| Lifetime | One render pass | One server request |
| In route handlers | No | No, the cache is only readable inside the component tree |
How fetch deduplication works
When a layout and a page call the same fetch, Next.js sends one network request and shares the response between them for that render.
// lib/posts.ts
export async function getPosts() {
const res = await fetch("https://api.example.com/posts");
return res.json();
}This helper fetches the posts once and returns the parsed body. Because it wraps fetch, Next.js memoizes it automatically inside a single render.
// app/posts/layout.tsx
import { getPosts } from "@/lib/posts";
export default async function PostsLayout({ children }: LayoutProps<"/posts">) {
const posts = await getPosts();
return <main><p>{posts.length} posts</p>{children}</main>;
}The layout needs the post count for its header, so it calls getPosts itself instead of receiving the data from the page below it.
// app/posts/page.tsx
import { getPosts } from "@/lib/posts";
export default async function PostsPage() {
const posts = await getPosts();
return <ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}Both files call getPosts, but the network is hit once. Pass an AbortSignal to opt out when you want a fresh call each time. This memoization lasts for a single render pass, not across requests.
How React cache() works
React cache turns an async function into a memoized version for Server Components. It deduplicates by argument and invalidates on every server request, so two components that call it with the same input run the work once.
// lib/user.ts
import { cache } from "react";
import { db } from "@/lib/db";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});A database query is exactly the case fetch deduplication cannot help with, because no fetch is involved. The memoized function is defined once in a shared module, which is what lets every component read from the same memo.
Now a layout header and a page body can both call getUser with the same id and the query runs once for that request. The second call returns the stored promise instead of touching the database, and the memo is thrown away when the request ends. The same wrapper works for any async helper, as shown in fetching data directly from a database.
Which one to use
If the data comes from fetch, do nothing: it is already deduplicated. If the data comes from an ORM, a database, or a custom async function, wrap it in cache so repeated calls within a request run once.
When the work is already covered by fetch deduplication, adding cache on top adds nothing. The two mechanisms overlap, but they are not interchangeable.
Common mistakes
- Calling a memoized function outside a component does not use the cache, because React only provides cache access inside the component tree.
- Defining cache inside a component creates a new memoized function every render, so no two calls ever share.
- Assuming fetch deduplication applies in route handlers. It does not, because route handlers are outside the component tree.
For how these fetch requests interact with the persistent framework cache, see using fetch in Next.js.
Rune AI
Key Insights
- fetch deduplication is automatic and covers fetch calls only.
- React cache wraps any async function and is opt-in.
- fetch memoization lasts one render, while cache lasts one server request.
- Neither applies outside the component tree.
- Use cache for ORM and database calls that fetch cannot see.
Frequently Asked Questions
Are fetch deduplication and React cache the same thing?
Does fetch deduplication work in route handlers?
When do I need React cache?
Conclusion
Next.js deduplicates fetch automatically within a render, while React cache is a manual wrapper for any async function. Use fetch's built-in behavior for HTTP, and wrap database or ORM calls in cache to get the same single-request guarantee.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.