"use cache"
Unanswered
Ts.Dimitrov posted this in #help-forum
Hello, please help. When i try to "use cache" in a function that my footer uses, the whole layout and all pages becomes cached for that perioud of time ( its shown in the build table )
export const getSideBarItems = cache(async (locale) => {
'use cache';
cacheLife('max');
const res = await fetch(
headers: {
'x-vercel-oidc-token':
'Accept-Language': locale,
},
});
return await res.json();
});
is this expected behaviour? Also, whe should we use "use cache" and when "cache" from react? Should they be combined?
export const getSideBarItems = cache(async (locale) => {
'use cache';
cacheLife('max');
const res = await fetch(
/items, {headers: {
'x-vercel-oidc-token':
Bearer ${process.env.VERCEL_OIDC_TOKEN},'Accept-Language': locale,
},
});
return await res.json();
});
is this expected behaviour? Also, whe should we use "use cache" and when "cache" from react? Should they be combined?
100 Replies
Should they be combined?yes its fine to combine them
react's cache dedeuplicate calls within single request
"use cache" persists cache in between several requests.
this is expected behabior. you used "use cache" in getSideBarItems that means if you call getSideBarItems in 5 different request, it will return the same data.
however i usually rather prefer putting react's cache inside "use cache".
in this case there isn't anything that is worth react's caching. react's cache() is usually done for expensive computation if done repeatedly like decoding JWT for auth (it only need to be done once but many function needs it)
and you usually put auth check outside of "use cache"
@alfonsüs ardani thank you for the asnwer, I maybe explained wrong however. Im using the function above in my Footer component, but it caches all the routes of and components on the same layout, not just the components that are using the getSideBarItems function. Like putting the "use cache" in this function, when im building my project, half the project gets cached because of it.
Also, could you please explain what do you mean with "single request" and "between server requests", not sure that i understand this.
Thanks in advance!
Also, could you please explain what do you mean with "single request" and "between server requests", not sure that i understand this.
Thanks in advance!
when you refresh the page thats 2 requests
are you able to give more context on where you put "use cache"? its hard to determine the setup. at this point im just guessing
can you be clearer by what you meant by "half the project"?
what do you mean by "half the proejct gets cached"?
what behavior are you experiencing?
Okay here is my layout
#Unknown Channel
<Suspense fallback={null}>
<Header locale={locale} />
</Suspense>
<ModalProvider />
<div className="inline-flex flex-row w-full ">
<Suspense fallback={null}>
<Sidebar locale={locale} />
</Suspense>
<div className="flex-in-col overflow-hidden min-h-dvh px-2 max-md:gap-4 min-w-0 w-full">
{children}
<FooterActionButtons />
<Footer locale={locale} />
</div>
</div>
</>
i'm using getSideBarItems in my Footer, if i try to cache it, when i run "npm run build" every page comes like this
Route (app) Revalidate Expire
│ ├ /en/change-password 30d 1y
│ └ /bg/change-password 30d 1y
│ ├ /en/contacts 30d 1y
│ └ /bg/contacts 30d 1y
│ ├ /en/offline 30d 1y
│ └ /bg/offline 30d 1y
etc..
I have the same exact problem in my <Sidebar> lets say, it has a component Submenu
export default async function Submenu({ locale }: { locale: string }) {
'use cache';
cacheLife('max');
const sideBarItems = await getSideBarItems(locale);
if i do the cache here, again the whole route and every page in this layout gets revalidated as the map above. So, i guess my question is, is this how it should work, and are we suppose to cache function/components that are in the layout? This seems to trigger revalidation on the whole route tree below
#Unknown Channel
<Suspense fallback={null}>
<Header locale={locale} />
</Suspense>
<ModalProvider />
<div className="inline-flex flex-row w-full ">
<Suspense fallback={null}>
<Sidebar locale={locale} />
</Suspense>
<div className="flex-in-col overflow-hidden min-h-dvh px-2 max-md:gap-4 min-w-0 w-full">
{children}
<FooterActionButtons />
<Footer locale={locale} />
</div>
</div>
</>
i'm using getSideBarItems in my Footer, if i try to cache it, when i run "npm run build" every page comes like this
Route (app) Revalidate Expire
│ ├ /en/change-password 30d 1y
│ └ /bg/change-password 30d 1y
│ ├ /en/contacts 30d 1y
│ └ /bg/contacts 30d 1y
│ ├ /en/offline 30d 1y
│ └ /bg/offline 30d 1y
etc..
I have the same exact problem in my <Sidebar> lets say, it has a component Submenu
export default async function Submenu({ locale }: { locale: string }) {
'use cache';
cacheLife('max');
const sideBarItems = await getSideBarItems(locale);
if i do the cache here, again the whole route and every page in this layout gets revalidated as the map above. So, i guess my question is, is this how it should work, and are we suppose to cache function/components that are in the layout? This seems to trigger revalidation on the whole route tree below
you can still have dynamic part of your app and those goes under <Suspense>
are we suppose to cache function/components that are in the layout?Yea thats fine, you can use nextjs's cache function/components everywhere.
when you run revalidateTag or revalidatePath, it will refresh the layout with updated data.
in cacheComponent, no longer are the days where staticity of a route is determined in a route. so staticity isn't defined vertically but now horizontally. at which part of the render tree do you draw the line of the boundary between static and dynamic? that is the question now with cacheComponent.
ok still unclear why using "use cache" in a function, caches all pages in the layout and wants to revalidate them
@Ts.Dimitrov ok still unclear why using "use cache" in a function, caches all pages in the layout and wants to revalidate them
Because in cacheComponent, page.tsx, and layout.tsx are static by default
the route will check the lowest cacheLife in the static boundaries of the route
the root files have static shell by default and it is static untill you put dynamic API under <Suspense>
what kind of behavior are you looking?
well my understanding is that caching a function allows you to reuse it in multiple components without hitting the backend over and over, since its cached. But in my cache, caching this function makes the all the pages in this current layout to be revalidated and cached. You can see the build map in my previous response. Maybe im not understanding properly
@Ts.Dimitrov well my understanding is that caching a function allows you to reuse it in multiple components without hitting the backend over and over, since its cached. But in my cache, caching this function makes the all the pages in this current layout to be revalidated and cached. You can see the build map in my previous response. Maybe im not understanding properly
well my understanding is that caching a function allows you to reuse it in multiple components without hitting the backend over and over, since its cachedYes caching does that.
But "use cache" isn't just "caching a function". it does more than that. it creates a cache boundary that affects its surrounding depending on where and how it is used.
@Ts.Dimitrov well my understanding is that caching a function allows you to reuse it in multiple components without hitting the backend over and over, since its cached. But in my cache, caching this function makes the all the pages in this current layout to be revalidated and cached. You can see the build map in my previous response. Maybe im not understanding properly
Are you understanding that in cacheComponent the root needs to be static? Static means cached.
and ultimately,
getSideBarItems will be cached regardless of where you used it. it will be reused in multiple components without hitting the backend over and overAgain. cacheComponent needs static shell. Static means cached. Static root doesn't mean your entire page is static. You have PPR. you can make some part of the route dynamic but it will still show static.
cacheComponent needs static shell. your route will have "revalidate" and "expire". If it doesn't have "revalidate" and "expire" that means it will never be revalidated and never be expired
Would it be okay to elaborate parts that aren't clear from my message?
what behavior are you expecting?
your "use cache" function does exactly that
im pretty much expecting this to revalidate just this function or at least that part of the component, not all my pages in this layout 🤷♂️ ty anyways
you can do that if getSideBar is used in a dynamic environment
but your getSideBar is used in a static shell
its been like this since next14, if you revalidate a function used in static route, it will revalidate the pages too
by dynamic enviroment do you mean having auth/cookies/headers used in the comp and having suspense baundary around it? Its how its curretly been used
no, your <Footer> resides outside of the <Suspense> boundary...
and your <Sidebar> needs to have dynamic API like header/cookies/connection
why is revalidating the whole page a problem? it means it just re-runs the minimum number of delay time needed to refresh the data. if you cache things properly, other data wouldn't be that much affected by this
because when im using use cache in another page/component, i cannot see if its cached and when it will be revalidated in the build map
if you put getSideBarItems and revalidate it by 30d then ofc the "static" part of your site is going to refresh every 30d
literally this getSideBarItems function make the whole map with its cache period
I see. so its not about "use cache" but the fact that nextjs doesn't have a logging on which part gets cached for how long
correct, im not even sure if this cache thing works in my other pages, just because i have it in this function, literally putting it there makes the whole layout and all pages in the build map with this revalidate period. In my "/" route i have component that is using cacheLife("days") and that is not even affected on the map, on the map it says 1d/1w which is incorrect
but this behavior has been like this since next14. if you have lower cacheTime one one thing then that time will be used
I will try to demonstrate you that it still works
here is the build log
here is setup
route
/ will revalidate in every 5 second but that doesn't mean all your data is refreshed every 5 secondsome will be fetched anew, some will use a cached result
i understand that, its unclear why my getSideBarItems function wants to revalidate 50 other routes for no reason. I just put suspense around the footer as you mentioned, same thing
@Ts.Dimitrov i understand that, its unclear why my getSideBarItems function wants to revalidate 50 other routes for no reason. I just put suspense around the footer as you mentioned, same thing
probably because your getSideBarItems is used in 50 other routes :/
it will not revalidate 50 other routes
your 50 other routes will be revalidated for 30d
since getSideBarItems is in the 50 other routes
when you access lets say /a/b/c
it will get
/layout.tsx;
/a/layout.tsx;
/a/b/layout.tsx;
/a/b/c/layout.tsx; and
/a/b/c/page.tsx
if layout.tsx has getSideBarItems then ofc all of its children will also get revalidated?
it will get
/layout.tsx;
/a/layout.tsx;
/a/b/layout.tsx;
/a/b/c/layout.tsx; and
/a/b/c/page.tsx
if layout.tsx has getSideBarItems then ofc all of its children will also get revalidated?
theose components are visible in all other routes, if thats what you mean. And that was my initial question, is that expected behaviour just because they are components in the layout, should they revalidate all other pages?
you are using the term wrong
putting the components in the layout will mark the route (and its childrens) to be able to be revaldiated. it will not revalidate all other pages
putting component in the layout will not revalidate other pages
but it will enable revalidations for that layout and all its children
if thats what you mean then yes
"why my getSideBarItems function wants to revalidate 50 other routes for no reason"
no your getSideBarItems will not revalidate 50 other routes
please dont confuse the terms.
there is an actual API that will revalidate your routes using
no your getSideBarItems will not revalidate 50 other routes
please dont confuse the terms.
there is an actual API that will revalidate your routes using
revalidatePath or revalidateTag and it will be confusing to assume what you meantok then i will consider the build map and its revalidate/expire notation false. I will be putting use cache and trust that it works, no matter what the map shows, because putting "use cache" in my function, makes every route to have 15m/1w revalidate/expire, then setting cacheLife("max") in lets say "/contacts", it will just not be visualized on the map ( since as you mention it takes the lowest revalidate number ) and will keep going
the build map just means the lowest time it will revalidate/expire. it is not false, it is just incomplete.
the default "use cache" cacheLife is 15m/1w. you can configure this in next.config.ts if i remember this correctly. Thats why it will always show 15m/1w
since 15m/1w is the lowest denominator from "max"
yep correct, im just giving an example with the default one
interesting
i apologize for my lack of understanding
its fine, im still not sure if we are on same page what i mean, but for me this is not working properly or im not doing it right
i guess i wont be caching any component or function that is being improted in the layout
hmmm why not?
that way the map will be clean and every comp will show its own cache life
because i have "use cache" (default) in my getSide.. function and all 50 routes are with 15m/1w, while at the same time i have "use cache" with cacheLife('max') in my "/" and based on the route, it does not even exists
and if you have to cache 50 components or routes or w/e you are doing, you will be only seeing the lowest number of cache ( which is a function )
no sense
😄
i think its a bit far fetched to avoid caching in layout just to make sure the map shows the correct/desired cache life
since the map barely shows any complete breakdown of your cached component
i mean hopefully there is improvement in that part but it is still working correctly (the caching part, not the displaying part)
@Ts.Dimitrov because i have "use cache" (default) in my getSide.. function and all 50 routes are with 15m/1w, while at the same time i have "use cache" with cacheLife('max') in my "/" and based on the route, it does not even exists
but how would it work? if you have getSide... at "default" and "default" is lower than "max",
do you want to override getSide... function to also "max"?
do you want to override getSide... function to also "max"?
no, but i want to see my revalidate/expire on the other pages, not the cachelife for my getSide function
i dont want to go through all pages manually every day to see what/where and for how much time im caching
oh...
i guess this is the reason this map is made
interesting
perhaps a better logging is desired
how would you verify "max" by logging? 🙂
lets say my getSide... function has the default use cache, and my /contatcs have "max", how should i verify that the "max" is applied, since on the map it will be 15m/1w ( the cache from the getSide func )
so with that being said, i should not cache any component in the layout so i can see that the "max" is applied to my /contacts
but you can always make a wrapper
that wrap cacheLife and print out the info which will be triggered on build
just giving out idea, something like
perhaps?
export const cacheLife = (name, opts) => {
if(process.env.NODE_ENV === "production") console.log(name, opts) // Todo: make this readable
nextCacheLife(opts)
}perhaps?
you can always define custom profiles based on usage like "posts" or "comments" or "roots" or "sidebar" so it is always clear 👀
right... anyways, we are not getting anywhere with this, while i have you here, any way i can see what part of my page is rendered static and what part dynamic? They showed some "Suspense" extension that i seems to not be able to find
what
how are we not getting anywhere? is the solution not working?
of course not, why would i need a wrapper for 50 pages when it something that should be visulized automatically on the map 😄
because it helps you and the nextjs team havent caught up into making improved logs
rather than avoiding a paradigm that they want you to use, why avoid it at all?