Dedupe server work
cache() dedupes within a request; an LRU reuses across requests.
React docsreact.dev
Reference: React.cache documentation
isaacsgithub.com/isaacs
Reference: node-lru-cache
Vercelvercel.com/docs
With Vercel's Fluid Compute: LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache.
From vercel-react-best-practices: server-cache-react and server-cache-lru.
Five components, one user
Press Render, then change the caller count.
Plain async functionBad
async function getCurrentUser()Wrapped in cache()Good
const getCurrentUser = cache(async () => …)import { cache } from 'react'
export const getCurrentUser = cache(async () => {
const session = await auth()
if (!session?.user?.id) return null
return await db.user.findUnique({
where: { id: session.user.id }
})
})
The argument equality trap
Call each version twice. Objects never match by value.
Inline objectBad
getUser({ uid: 1 })PrimitiveGood
getUser(1)Same referenceGood
getUser(params)const getUser = cache(async (uid: number) =>
db.user.findUnique({ where: { id: uid } })
)
getUser(1); getUser(1) // 1 query: Object.is(1, 1)
getUser({ uid: 1 }); getUser({ uid: 1 }) // 2 queries: two different objects
const params = { uid: 1 }
getUser(params); getUser(params) // 1 query: same reference
Back-to-back requests
Send A, then B. Then jump past the TTL.
t = 0:00
React.cache onlyBad
cache(getUser)React.cache + LRUGood
new LRUCache({ ttl })1 instanceimport { LRUCache } from 'lru-cache'
const cache = new LRUCache<string, any>({
max: 1000,
ttl: 5 * 60 * 1000
})
export async function getUser(id: string) {
const cached = cache.get(id)
if (cached) return cached
const user = await db.user.findUnique({ where: { id } })
cache.set(id, user)
return user
}
Why it matters, pros and cons
Why it matters
Layout, header and page all ask for the user. cache() makes it one call.
Pros
- No prop drilling to avoid duplicate queries.
- Concurrent callers share one pending promise.
- An LRU turns A-then-B into one query.
Cons
fetch()is already memoized; wrapping it adds nothing.- Object arguments miss silently; pass primitives.
- An LRU serves stale data and needs tenant-scoped keys.