Skip to content

Nested unstable_cache results are not cached. #80532

Closed
@khobiziilyes

Description

@khobiziilyes

Link to the code that reproduces this issue

https://github.com/khobiziilyes/unstable-cache-reprod

To Reproduce

  1. Start the app in preview mode (npm run preview).
  2. Open the main page (http://localhost:3000/)
  3. Visit Brand 1 page (http://localhost:3000/1)
  4. Visit Brand 2 page (http://localhost:3000/2)

Current vs. Expected behavior

I've tried to simplify the flow of the issue as much as possible.

The project consists of 6 main commits. The first 2 are unimportant (t3app bootstrapping). The 3rd commit introduces the minimal and most basic structure of the app, which is:

The app has 3 main "api" functions (apis.ts)

  • getBrands: returns an array of brandId.
  • getBrandDetails: receives the id of the brand, returns the name and description.
  • getSuggestedBrands: For simplicity, it returns all brands except the one you supply. In a real case scenario, it would calculate the similarity to the other brands using the Levenshtein algo. Notice that THIS IS the critical function, since it implements another function, getBrands, inside it.

All of those have a console.log to simplify debugging.

The main page of the app (/) displays a list of links to all the brands available, which are retrieved using getBrands().
Each Brand page (/[brandId]) displays the details of the current brand, and also displays other suggested brands to visit which is a Suspended Server component that uses getSuggestedBrands.

If we run our app (npm run preview) and visit the main page, we'll notice that getBrands is not called (The whole page is static on build, the code is not running at all).

Now, we want to view a single brand page. If we visit the brands 1, 2, and 3 respectively, we'll see those logs:

Image

And this is perfectly normal since we are not doing ISR.
Notice that each getSuggestedBrands calls getBrands and uses that value.

Now, since those "api" calls are expensive, we have decided to add a cache layer to those functions, and since they are not "fetch" calls. We'll use unstable_cache instead (not React.cache because we want it to be shared among all requests). And that's what the 4th commit is for, we wrapped the getBrands and getBrandDetails in unstable_cache. We will rebuild the app and visit the brands pages again, we will notice those logs:

Image

We can notice that getBrands is not called anymore, because it is already called at build time and cached. And now the cached value is used in all calls, including the ones that getSuggestedBrands invokes.

Image

If we refresh the page /3, we will even notice that getBrandDetails(3) is not called anymore, since the first visit to the url invoked and cached the value. Super!

However, we have one more function left that is not cached, and in real life, such function will consume a lot of processing power, and we'd love to cache the final result of it. getSuggestedBrands, this function has only 1 parameter, currentBrandId, which it uses to find more interesting brands similar to that one.

Commit 6: Introduces the issue.

Image

After we wrap the function (which inherently uses another cached function getBrands), visit the pages, we'll get those logs:

Image

getBrands is called over and over ... If we try to use this function on its own, we'll get the cached value, but if we use it inside getSuggestedBrands, the cached value is not retrieved.
getSuggestedBrands is cached as expected.

Provide environment information

This project is configured to use npm because /Users/ilyesk/Developer/unstable-cache-reprod/package.json has a "packageManager" field

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.6.0: Thu Mar  6 22:08:50 PST 2025; root:xnu-10063.141.1.704.6~1/RELEASE_ARM64_T8112
  Available memory (MB): 16384
  Available CPU cores: 8
Binaries:
  Node: 22.15.0
  npm: 11.4.1
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 15.4.0-canary.82 // Latest available version is detected (15.4.0-canary.82).
  eslint-config-next: 15.3.3
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.8.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Use Cache

Which stage(s) are affected? (Select all that apply)

next start (local), Vercel (Deployed), next dev (local)

Additional context

Before each preview, I run rm -rf .next/ to make sure no cached values will mess with the reproduction.

I haven't dug deep into Next.js code, but I'd like to help resolve this issue if possible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    invalid linkThe issue was auto-closed due to a missing/invalid reproduction link. A new issue should be opened.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions