Dedupe server work

cache() dedupes within a request; an LRU reuses across requests.

React docsreact.dev
Reference: React.cache documentation
API referenceRead docs ↗
isaacsgithub.com/isaacs
Reference: node-lru-cache
lru-cacheView source ↗
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.
Fluid ComputeRead docs ↗

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 instance
import { 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.