The new cache model in Next.js is much more explicit than the older ISR/fetch-cache world. The important mental model is that "use cache" is not just caching fetches. It is caching the result of a server component or async function execution, including rendered RSC payloads and serialized return values.
use cache vs use cache: remote| Behavior | "use cache" | "use cache: remote" |
|---|---|---|
| Storage location | In-memory LRU cache inside the running Next.js process | Remote/distributed cache |
| Shared across server instances | No | Yes |
| Survives instance restart | No | Usually yes |
| Network lookup required | No | Yes |
| Extra infrastructure cost | None | Yes |
| Best for | Hot local reuse | Durable/shared caching |
| Sensitive to cold starts | Very | Much less |
| Works well with Fluid Compute | Yes | Yes |
| Default behavior | Local opportunistic caching | Distributed cache layer |
The really important distinction:
1"use cache"is essentially:
“memoize this result inside this running server instance”
while:
1"use cache: remote"is:
“store this cached result in shared infrastructure”
That difference completely changes the behavior at scale.
The cache key is effectively composed from:
1async function getProduct(id: string) {
2 "use cache"
3 return db.products.find(id)
4}roughly becomes:
1cacheKey = hash(
2 buildId +
3 functionId +
4 serializedArgs(id)
5)If you do this:
1getProduct("123")and later:
1getProduct("456")those are different cache entries.
Likewise:
1getProducts({
2 category: "shoes",
3 sort: "price",
4 page: 8,
5 filters: [...]
6})can create massive key cardinality very quickly.
One of the more subtle aspects of "use cache" is that cache keys are not always built solely from the explicit function arguments. Variables captured from outer scope can also influence the cache key.
Example:
1function createProductFetcher(region: string) {
2 return async function getProduct(id: string) {
3 "use cache"
4
5 return fetch(
6 `https://api.example.com/${region}/products/${id}`
7 ).then(r => r.json())
8 }
9}Here:
1const usFetcher = createProductFetcher("us")
2const euFetcher = createProductFetcher("eu")Even though:
1usFetcher("123")
2euFetcher("123")use the same function body and same argument ("123"), they produce different cache entries because region is closed over.
Conceptually the cache key becomes something like:
1hash(
2 functionId +
3 args("123") +
4 closedOverValues("us")
5)vs:
1hash(
2 functionId +
3 args("123") +
4 closedOverValues("eu")
5)This becomes even more subtle in component trees.
Example:
1export async function ProductGrid({
2 locale,
3 currency
4}) {
5 const formatter = new Intl.NumberFormat(locale, {
6 style: "currency",
7 currency
8 })
9
10 async function getProducts(category: string) {
11 "use cache"
12
13 const products = await fetchProducts(category)
14
15 return products.map(p => ({
16 ...p,
17 formattedPrice: formatter.format(p.price)
18 }))
19 }
20
21 return getProducts("shoes")
22}Here, formatter is closed over by the cached function.
That means:
localecurrencySo these produce different cache entries:
1locale=en-US currency=USDvs:
1locale=fr-FR currency=EUReven though:
1getProducts("shoes")was called with the same explicit argument.
This becomes dangerous with accidental high cardinality.
Example:
1export async function SearchPage({ searchParams }) {
2 const userSession = await getSession()
3
4 async function getResults(query: string) {
5 "use cache"
6
7 return performSearch({
8 query,
9 userId: userSession.id
10 })
11 }
12
13 return getResults(searchParams.q)
14}At first glance, it looks like the cache key is just:
1queryBut because userSession is closed over, every user effectively gets separate cache entries.
Conceptually:
1hash(
2 query +
3 userSession.id
4)Now your cache cardinality explodes.
That is one of the easiest ways teams accidentally destroy cache efficiency with "use cache" or "use cache: remote".
My rule of thumb:
If a cached function references:
This is probably the single biggest operational risk with the new cache system.
High cardinality means:
Too many unique cache keys with too little reuse.
Examples: