Understanding next.js caching
Unanswered
Asian black bear posted this in #help-forum
Asian black bearOP
I am trying to understand how to correctly use the caching in next 13.
The way I currently see it there is a way to cache everything forever, nothing at all, or revalidate after a certain time.
There doesn't seem to be a option anymore to set your own cache headers and set a cache for a certain time.
In my use-case I would want to cache the results of a fetch in a sever component for a certain time and get a fresh response for the first request after the time runs out.
Using Incremental Static Regeneration it returns the stale result for the first request after the time ran out, causing that user to see an (eventually very) outdated page.
Example:
I have a page with data that generally updates every 5 minutes. During the 5 minutes I want to get a cached version of the fetch or the entire page. Using static regeneration, i face the issue that if no user calls the page for some time (lets say 30min), the first user that looks at the page and triggers the revalidate, receives a very outdated page.
I basically want to achieve what was previously done by using something like
Also: It seems that calling a page directly and navigating to it via a <Link> behaives differently and doesn't revalidate the page at all. Is this a intended behavior?
The way I currently see it there is a way to cache everything forever, nothing at all, or revalidate after a certain time.
There doesn't seem to be a option anymore to set your own cache headers and set a cache for a certain time.
In my use-case I would want to cache the results of a fetch in a sever component for a certain time and get a fresh response for the first request after the time runs out.
Using Incremental Static Regeneration it returns the stale result for the first request after the time ran out, causing that user to see an (eventually very) outdated page.
Example:
I have a page with data that generally updates every 5 minutes. During the 5 minutes I want to get a cached version of the fetch or the entire page. Using static regeneration, i face the issue that if no user calls the page for some time (lets say 30min), the first user that looks at the page and triggers the revalidate, receives a very outdated page.
I basically want to achieve what was previously done by using something like
res.setHeader('Cache-Control', public, max-age=300, s-maxage=200'
in the getServerSideProps function.Also: It seems that calling a page directly and navigating to it via a <Link> behaives differently and doesn't revalidate the page at all. Is this a intended behavior?
59 Replies
Asian black bearOP
bump
Philippine Crocodile
does export const revalidate = 0.2 work?
Asian black bearOP
No, that wouldnt solve anything. A shorter revalidation time still has the same problems I descripted in the example.
If the page isn't visited for a longer time, the first person visiting it, gets a outdated page.
If the page isn't visited for a longer time, the first person visiting it, gets a outdated page.
Also: You can't even set it below 1:
Asian black bearOP
bump
I can't image that there is no solution for this? For me this was always the default use case for pages with changing data?
The way I see I only have 3 solutions:
- Use no cache, causing performance issues and a lot of unnecessary fetch requests
- Don't use SSR and fetch everything client-side where I can set cache control headers, causing a worse user experience
- Going back to Next.js 12 or going back to the pages folder
I can't image that there is no solution for this? For me this was always the default use case for pages with changing data?
The way I see I only have 3 solutions:
- Use no cache, causing performance issues and a lot of unnecessary fetch requests
- Don't use SSR and fetch everything client-side where I can set cache control headers, causing a worse user experience
- Going back to Next.js 12 or going back to the pages folder
Asian black bearOP
bump
where are you setting revalidate exactly
@Asian black bear out of curiosity, very outdated means the data cached at build time?
or the last fetched data?
Asian black bearOP
Meaning the last fetched data.
Let's again say I have data that updates around every 5 min and therefore have a revalidation time of 5 minutes (300 seconds):
User 1 visits the page and gets the current data.
User 2 visits the page 2 minutes later and gets the cached data (which is ok)
User 3 visits the page 50 minutes later and triggers the revalidate, but still gets the data from 52 minutes ago (which is very outdated)
Let's again say I have data that updates around every 5 min and therefore have a revalidation time of 5 minutes (300 seconds):
User 1 visits the page and gets the current data.
User 2 visits the page 2 minutes later and gets the cached data (which is ok)
User 3 visits the page 50 minutes later and triggers the revalidate, but still gets the data from 52 minutes ago (which is very outdated)
in the page.tsx with
export const revalidate = 60
, but I also tried setting it in the fetch, which obviously results in the same outcomeyeah, i know. it's like Stale-While-Revalidate in workbox. unfortunately, no way to garbage collect cache data in a certain period of time. or you would poll the endpoint periodically to keep it fresh.
Asian black bearOP
Bump 😦
Asian black bearOP
Bump 😦
Asian black bearOP
Bump
Asian black bearOP
Bump
Asian black bearOP
Bump
Asian black bearOP
Bump
Asian black bearOP
Bump
Asian black bearOP
Bump
Saltwater Crocodile
I'm not familiar with Next.js but am looking at some infrastructure stuff. To be honest I'm not super comfortable with how much Next.js is taking on as a responsibility. Maybe there is nothing that can be done about this, but I'd rather have my caching system on top of Next.js. I'd like to have hooks I suppose to be able to deeper integrate caching with more granular fetches (e.g. a fully rendered page vs something more granular) in Next.js, but I think the default behavior should be "you do your own caching".
I might be totally off the mark though.
(free bump at least ;))
I might be totally off the mark though.
(free bump at least ;))
Asian black bearOP
Bump
Asian black bearOP
Bump
if the behavior you need isn't available by the caching methods of the new
fetch
, you can change it to never cache anything and store the data in an external service (like upstash or your own) to cache it instead, where you have more controlI am not sure there is a way to set the
Cache-Control
header from a page like you could do with getServerSideProps
since the new headers
function is read only, but I am pretty sure you can still do that in the middleware so if you just want browser cache you might want to give it a tryBarbary Lion
i came across a recent PR for updating the docs for explaining how caching works. maybe there's something here that is relevant? https://github.com/vercel/next.js/blob/canary/docs/02-app/01-building-your-application/04-caching/index.mdx#L11
Asian black bearOP
While it's very cool that this is now nicely documented, it just sums up the things I already know/tried and doesn't provide a solution to the use case I described in my first message. Basically, I either want to set the cache headers myself or use the Data Cache, but with a more aggressive revalidation approach. Meaning it should already be served to the first request coming in after the revalidation time has passed.
Feel free to correct me if I missed something, though.
Feel free to correct me if I missed something, though.
That's kinda the way I am currently forced to to this, but it's just so weird to me that Next.js removed the, in my opinion the best and most obvious way,to handle caching (setting cache headers).
And then try to present Caching as a cool feature which the app router re-invented and can perfectly manage.
Like I can't repeat myself enough. This usecase is not something thats extremely rare. It's the default.
Frequently changing data in detail pages (dynamic urls) that should be cached for a certain amount of time.
And then try to present Caching as a cool feature which the app router re-invented and can perfectly manage.
Like I can't repeat myself enough. This usecase is not something thats extremely rare. It's the default.
Frequently changing data in detail pages (dynamic urls) that should be cached for a certain amount of time.
Asian black bearOP
Bump
What was your question?
Asian black bearOP
Basically I want a cache that does the following:
- Cache stuff for a certain time
- If a request comes in after that time, fetch fresh data and cache that for the defined time. It's important, that the first request after the cache time gets fresh data otherwise if a page isnt visited for a longer time, the first user gets extremely outdated data.
or a way to let me set my own cache headers.
- Cache stuff for a certain time
- If a request comes in after that time, fetch fresh data and cache that for the defined time. It's important, that the first request after the cache time gets fresh data otherwise if a page isnt visited for a longer time, the first user gets extremely outdated data.
or a way to let me set my own cache headers.
I dont think thats possible with the current state of persistent data cache.
I mean natively
this diagram explains how the data cahing works in next.js
Asian black bearOP
Yeah, seems that way...
Thanks for the help though. Sad that there is no solution for this case, as I think this is quite a common use case.
Thanks for the help though. Sad that there is no solution for this case, as I think this is quite a common use case.
you could try bring it up in the next.js github issue
Asian black bearOP
frankly speaking, the decision in the revalidation mechanism seemed kinda mysterious
don't use revalidation, but instead use a cron job to call
revalidatePath()
/revalidateTag()
after that certain amount of timeit should do exactly as you want
Tramp ant
by doing this you still get stale data on first request and revalidation is triggered on the background.
Ideally first request would not get stale data, show loading state while server re-renders
Ideally first request would not get stale data, show loading state while server re-renders
It always gets the newest data right after invalidation
On demand revalidation in the app router is not the same as in the pages router
Tramp ant
not when working with ISR
the docs are a bit misleading in that sense. It works that way when working with SSG and SSR but ISR cache is not purged.
in my case its not terminal so I'm still using as is but its what the OP is talking about as well.
When using ISR, first request after any kind of invalidation returns stale data, second request returns regenerated page
When using ISR, first request after any kind of invalidation returns stale data, second request returns regenerated page
this is why they suggested to use on-demand revalidation with a cronjob instead of revalidation, which doesn't have this issue
Tramp ant
Have you guys tried it yourselves? Cron-job or manually triggered on-demand revalidation (revalidateTag, revalidatePath) still returns stale data on first request when using ISR
yeah I just tested it with my project
tbf I tried doing this a few weeks ago and it didn't work, but I just tried with latest canary and it is working
previously I had an API Route in the
pages
dir with await res.revalidate('/')
because revalidatePath('/')
wasn't workingTramp ant
thanks it did work! didnt realize there was a canary release yesterday.
quick question do you get 404 after on-demand revalidation, if you build and start server locally?
works fine on vercel though
quick question do you get 404 after on-demand revalidation, if you build and start server locally?
works fine on vercel though
Yes I have a prod app running this that’s why I could answer that confidently. I want to have background revalidation with on-demand revalidation but that’s not possible and the slow first post-revalidation request still bites me, but it’s exactly what you need that’s why I answered
Southern rough shrimp
Can you summarise what you did? 😅
Tramp ant
I set up a revalidate enpoint that when called will to call
in my case it didnt work until updating tonextjs 13.4.13
revalidatePath('path/i/want/to/revalidate')
in my case it didnt work until updating tonextjs 13.4.13