Description
Link to the code that reproduces this issue
https://github.com/khobiziilyes/unstable-cache-reprod
To Reproduce
- Start the app in preview mode (npm run preview).
- Open the main page (http://localhost:3000/)
- Visit Brand 1 page (http://localhost:3000/1)
- 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 ofbrandId
.getBrandDetails
: receives theid
of the brand, returns thename
anddescription
.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:
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:
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.
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.

After we wrap the function (which inherently uses another cached function getBrands
), visit the pages, we'll get those logs:
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.