The cache function from React lets you memoize expensive data fetches or computations within a single server request.
The Problem
Without caching, the same data might be fetched multiple times:
tsx// Both components fetch the same user! async function UserHeader({ userId }) { const user = await getUser(userId); // Fetch #1 return <h1>{user.name}</h1>; } async function UserSidebar({ userId }) { const user = await getUser(userId); // Fetch #2 - duplicate! return <aside>{user.bio}</aside>; }
The Solution: cache()
tsximport { cache } from 'react'; // Wrap your data fetching function const getUser = cache(async (userId) => { console.log('Fetching user:', userId); const response = await fetch(`/api/users/${userId}`); return response.json(); }); // Now both components share the same fetch async function UserHeader({ userId }) { const user = await getUser(userId); // Fetches return <h1>{user.name}</h1>; } async function UserSidebar({ userId }) { const user = await getUser(userId); // Uses cached result! return <aside>{user.bio}</aside>; }
How cache() Works
- First call: Executes the function, stores the result
- Subsequent calls (same arguments): Returns cached result
- Request ends: Cache is cleared
tsxconst getUser = cache(async (id) => { console.log('Fetching:', id); return await db.users.findUnique({ where: { id } }); }); // During a single request: await getUser(1); // Logs: "Fetching: 1" await getUser(1); // No log - cached! await getUser(2); // Logs: "Fetching: 2" await getUser(1); // No log - still cached!
Practical Example: Shared Data
tsximport { cache } from 'react'; import { cookies } from 'next/headers'; // Cache the current user for the entire request export const getCurrentUser = cache(async () => { const token = cookies().get('auth-token')?.value; if (!token) return null; const user = await verifyToken(token); return user; }); // Use anywhere in your Server Components async function Header() { const user = await getCurrentUser(); return <nav>{user ? `Hi, ${user.name}` : 'Sign in'}</nav>; } async function Sidebar() { const user = await getCurrentUser(); // Same user, no extra DB call return <aside>{user?.preferences}</aside>; } async function MainContent() { const user = await getCurrentUser(); // Still cached! return <main>Welcome back, {user?.name}</main>; }
cache() vs Other Caching
| Method | Scope | Duration |
|---|---|---|
cache() | Single request | Request lifetime |
fetch cache | Across requests | Configurable |
unstable_cache | Across requests | Configurable |
| External cache (Redis) | Global | Configurable |
Important Notes
- Request-scoped: Cache is cleared after each request
- Argument-based: Different arguments = different cache entries
- Server only: Only works in Server Components
tsx// Arguments matter! const getData = cache(async (id, options) => { ... }); await getData(1, { full: true }); // Cache miss await getData(1, { full: true }); // Cache hit await getData(1, { full: false }); // Cache miss - different options!