Next.js Discord

Discord Forum

How not to show stale cache, but loading state and fresh data instead?

Unanswered
New Guinea Freshwater Crocodile posted this in #help-forum
Open in Discord
Avatar
New Guinea Freshwater CrocodileOP
I am loading and showing external images on a page. The image urls expire after a certain time. Using revalidate, I can update the cache, but the stale cache that is shown has expired image urls. They only start working again after a refresh.

Is there a way to disable the display of the stale cache and show the loading state instead, if the page has to be revalidated?

129 Replies

Avatar
New Guinea Freshwater CrocodileOP
Basically the gist of it is, that there is currently no way to do a syncronous rerender of a page and display a loading state, if it is cached on the server. If the cache is stale, the rerendering is done in the background and the cache is served.

I don't know if there is a streaming update when you are already on the page and just use a <Link /> to navigate to the stale page. But if you hit the stale page directly, there is no update of the data.

But even if there was a streaming update, there should be an optino not to show the stale cache but the loading state again. In some cases it might be beneficial to show loading instead of the outdated page.

I would love some more input on this topic.
Avatar
ultrawelfare
I'm facing the same issue and I just resided to live with it. However this is not a very preferable solution and I'd love if there was some way of forcing the (router?) to display fresh data.
Avatar
New Guinea Freshwater CrocodileOP
a cron job is a workaround to make sure the page is always up to date for the user if you have a known revalidation interval. but it feels very dirty, hovever better than having the page rendered on every request. I feel like the caching needs some more love.
That works for the server cache only though - your problem seems to be client side
so basically you would also need to tell the router to use the new data on back nav
Avatar
ultrawelfare
It is dirty.. Maybe alongside the export const revalidate = N there would also be export const revalidateOnRequest = bool that would change the refresh strategy? I'm not completely sure of the internals however it's a huge pain point for me at the moment, and unfortunately I'm considering switching to anything else :/
Feels like the new app router is designed to fit a specific niche. Too much problems from my end compared to NextJS <13
Avatar
New Guinea Freshwater CrocodileOP
exactly what i was thinking, an opt in to do the rerender syncronously and not in the background
i have just started with 13, vue guy here, using the pages directory, could we easily create the needed behavior?
Avatar
ultrawelfare
It's been some years (before On Demand Revalidation!), so my memory might be wrong but I think yes you could...

I'm relooking the documentation and:

When a request is made to a page that was pre-rendered at build time, it will initially show the cached page.

Any requests to the page after the initial request and before 10 seconds are also cached and instantaneous.

After the 10-second window, the next request will still show the cached (stale) page

Next.js triggers a regeneration of the page in the background.

Once the page generates successfully, Next.js will invalidate the cache and show the updated page. If the background regeneration fails, the old page would still be unaltered.
Avatar
New Guinea Freshwater CrocodileOP
Once the page generates successfully, Next.js will invalidate the cache and show the updated page. If the background regeneration fails, the old page would still be unaltered.

Initially i understood that the new content is streamed after regeneration, but that is not the case afaik. so the user that triggered the regeneration does not receive the new content
"the old page would still be unaltered" just means the cache is not updated on the server i guess
Avatar
ultrawelfare
Well I understand the same too !

Just checked, it's copy pasted into the app router documentation as well.
Image
Either I don't remember correctly and was never working this way, or something internally changed in app router that (broke?) it?
Avatar
New Guinea Freshwater CrocodileOP
so yeah, sadly this is not suitable for use cases where you do not want to show stale content but also want to use caching
to get this working the page would need to be declared dynamic and ssr on every request, which might be problematic for the api calls
all comes back to the opt in into revalidatin/rerendering when requesting the page
but i am so grateful to have found somebody with the same issue, had a long discussion last night in #discussions about this and could not get my point across XD
Avatar
ultrawelfare
I'm also having another cache related issue (https://nextjs-forum.com/post/1121336097104986212)
I literally just wanted to do a simple dashboard, and in every function call that i write I have some kind of problem 😛
At this point deploying a PHP website with htmx might be less time consuming
Avatar
New Guinea Freshwater CrocodileOP
Yeah, it is these little gotchas that break development for now. I am sure it will be addressed soon. check this out: https://github.com/vercel/next.js/issues/42991 i think that is the same problem you are having
Avatar
ultrawelfare
Damn november 16 2022!
This is so breaking, especially when doing pagination (one of the most basic features)
Best case scenario, you forget server side stuff and you just do an api route with client side fetching
Avatar
New Guinea Freshwater CrocodileOP
and that is actually the part that made me try next in the first place, because i think it can be great once ironed out
Avatar
New Guinea Freshwater CrocodileOP
maybe give astro a go if it is a thing you are building from scratch and not too big, you can use all your react code just without the caching issues i think. and then migrate back to next once the issue is gone.
but then again, if auth etc is involved, astro is not the way to go
Avatar
Alfonsus Ardani
nextjs revalidation triggers when you refresh the page or do a hard navigation. you can't have it revalidate real time without using client components
Avatar
New Guinea Freshwater CrocodileOP
i am not looking for real time revalidation, i just want the regeneration to run in the foreground when i visit a page. meaning that i get the loading state until the fresh content rolls in and not a stale cache. once i am on the page, no refreshing should happen, it is just about the first visit to the page
Avatar
ultrawelfare
Example:

# What happens now :

Website has currently cache A loaded.
1. User visits site -> get served A
2. Time has passed and revalidation needs to happen at the server on the next request
3. User visits site -> gets instantly served A, revalidation background job starts to create cache B with new data
4. Revalidation background job ends and creates cache B with new data for the next request


# What we are asking for:

Website has currently cache A loaded.
1. User visits site -> get served A
2. Time has passed and revalidation needs to happen at the server on the next request
3. User visits site -> revalidation background job starts to create cache B with new data... User gets shown "loading.js" until this job is complete and is shown cache B
Avatar
Alfonsus Ardani
hmmmm
any example?
since i can't reproduce these result
Avatar
ultrawelfare
I'm currently at work, I'll see if I can make a reproduceable example by the end of the day.

However it isn't anything too fancy. Just a async page with a revalidate on it
Avatar
Alfonsus Ardani
https://nextjs-demos-git-dynamic-routes-alfonsusac.vercel.app/apple such as this, everytime i refresh, the loading.ts is always shown
although i intentionally waited for 2 seconds
Avatar
ultrawelfare
I would link my live website, but I don't know if there's any rule of rejecting promotes etc
But in my live website I have a last update field
I just opened the website (12:29 GMT)
The website shows: Last Update: Thu, 22 Jun 2023 12:26:54 GMT
My revalidation time is 30seconds
Seems like its launching the "revalidation" job in the background and I got served an old cache
Avatar
New Guinea Freshwater CrocodileOP
I dont think it is promotion if you post it here as it would help to display the case
I am not an mod though 🙂
Avatar
ultrawelfare
Well if many people join, we wouldn't see the bug ahahahhaha
The more people do requests, the less obvious it is
Avatar
New Guinea Freshwater CrocodileOP
I can whip up an example on vercel
give me a few mins
Avatar
New Guinea Freshwater CrocodileOP
the getDate function could be async, does not change anything, behavior is the same, that is why i opted for the very simple demonstration here. breaks if we all refresh at the same time, but i think we will not do this haha
gist is, usually you will have to refresh twice to update the time
Avatar
ultrawelfare
Can confirm I['m also doing the same thing, just with an API call inside the getDate()
Wait, could it be of the missing loading.tsx ?
I don't remember if mine had it, because I did many iterations to try to find the problem
Maybe NextJS doesn't find a loading.tsx and is like, eh I'm gonna serve you some old data i have, because i have nothing else to show you
Avatar
Alfonsus Ardani
interesting...
Avatar
New Guinea Freshwater CrocodileOP
no should not be the missing loading, i will add one
is updated, with loading
but you will never see the loading, as it uses the cache from build time first, and then the cache created by our relentless refreshing
Avatar
Alfonsus Ardani
uhh
tim?
Avatar
New Guinea Freshwater CrocodileOP
i have also tried using the cache function, but that does not have a second argument like fetch to revalidate the request
Avatar
Alfonsus Ardani
nextjs doesn't have root loading page.
Avatar
New Guinea Freshwater CrocodileOP
yes thats me :d
Avatar
Alfonsus Ardani
loading.tsx for the root layout is for the children route
thats why my demo is showing child route loading.tsx
i thought the IMAGE component thing is something that you want inside a child route 🙃
Avatar
New Guinea Freshwater CrocodileOP
i dont think that is an issue to be honest, the caching would be the same for the route, woudnt it? even if there is no loading state
Avatar
Alfonsus Ardani
yeah it is
lol
not sure how enxtjs handle revalidation
whether if its per request or if the revalidation is stored server-wide
Avatar
New Guinea Freshwater CrocodileOP
nocache has revalidate = 0, cache has revalidate = 5.
nocache always is ssr, shows loading
cache is same code, except for the revalidate = 5 - and you have to refresh twice again
Avatar
Alfonsus Ardani
but the behavior is consistent if you wait for 10second
it works if i wait longer
but no loading screen though
Avatar
New Guinea Freshwater CrocodileOP
i dont think it works, maye we refreshed both - i have added a 1s delay in the get date function, so either it has to load or show the old content
nocache should show loading
Avatar
Alfonsus Ardani
nocache is what im familiar with
but caching and ISR, eugh...
Avatar
New Guinea Freshwater CrocodileOP
i dont really think there is a solution yet, but that displays the issue as you have to refresh twice (or more, depending on the time the api takes in the background to regenerate the page)
Avatar
Alfonsus Ardani
what if you use Suspense?
to manually show the Loading page while the chidl component is awaiting to render
Avatar
New Guinea Freshwater CrocodileOP
the child, which is generated on the server, loads instantly as the cached version is sent
so it would not change the behavior
actually i think the whole page is wrapped in suspense and the loading.tsx is used as a placeholder
so the logic is already there in this waay
Avatar
Alfonsus Ardani
i know but nextjs is doing some caching magickery
Avatar
New Guinea Freshwater CrocodileOP
😄
Avatar
Alfonsus Ardani
just like how in vercel
they always show the previous version right at the moment it is being ISR'd
oh maybe thats why
Avatar
New Guinea Freshwater CrocodileOP
yes that is the whole problem, you cannot opt out of this behavior.
Avatar
Alfonsus Ardani
Image
its an old behavior from the pages dir 🙃
if i were you id just deal with it imo
would love to see in the fetch option to opt-out of this behavior
Avatar
New Guinea Freshwater CrocodileOP
unstable_cache
might be the savior maybe
as it gets the same revalidate like fetch. have to try fetch behavior with revalidate, if it does what we need. could build a workaround where fetch calls an internal api route?
feels dirty though, will try
Avatar
New Guinea Freshwater CrocodileOP
revalidate in fetch seems to work the same way sadly
(╯°□°)╯︵ ┻━┻
Avatar
ultrawelfare
yep i've tried both, and also having the same problem
Avatar
New Guinea Freshwater CrocodileOP
so, only way is to use dynamic SSR in those cases, no caching, then it works, just more strain on the api
Avatar
ultrawelfare
use a lighter framework 😆
Avatar
New Guinea Freshwater CrocodileOP
Hehe, thing is I am just validating if I might use next for future projects. I really want to. I like the direction it is headed. But small things like this make me hesitate…
Avatar
ultrawelfare
Well to be fair you can always reside to using only SSR (as done previously or by many other frameworks) or doing it on the clientside
The problem is not the technology, the problem is the documentation that pushes you a certain why which might not be a solution to your problem
I assume being a beginner trying out next, must be very confusing nowadays with the pages / app dichotomy
You could technically say that with any framework, you just pick a different poison
Avatar
New Guinea Freshwater CrocodileOP
well, the good thing is, that i have touched next for the first time after the app router being stable, so i have not learned anything about the pages router and it was easy to understand what is going on as i didnt have to unlearn anything. i could easily implement an own cache for the api call that just stores the result in memory. <- that is what i would say if i knew the deployment was just a running node process. however i am not sure with vercel, there is a lot of magic and serverless stuff going on, so there might not be a persistent memory. have to try that.
and the last thing i want to do is build parts that are relying on the way it is deployed, that will come back to haunt me at some point
that uses a module that just holds a count that increments with each call - if there is just a long running node process, it should always increment when reloading. if not, it should reset at some point to 0
but it looks like it works like expected
Avatar
New Guinea Freshwater CrocodileOP
so what i gathered in #discussions - a serverless function is running, but it might shut down at any time resetting the counter or it might scale up and then we have two counters. but that still means, that if we do own in memory caching of the results and have an SSR route, the api request will be made much less then using the build in fetch SSR
i would say, better, not perfect, but very easy to implement