I noticed that even though I no longer use generateStaticParams in my codebase (removed August 16th), I have still had over 170,000 ISR writes since removing all generateStaticParams() usage in my codebase. My assumption is it’s something in my [slug] page https://github.com/JamesSingleton/redshirt-sports/blob/main/apps/web/app/[slug]/page.tsx but I honestly have no idea.
I switched as much as I could to use date-fns and date-fns-tz. However, I still need it for new Date().getFullYear() for things like copyright in my ld+json scripts as well as the footer.
Is there even a replacement for this?
Vercel Staff
Hi , sorry for the late response.
Can you check in Dashboard > Your project > Observability > ISR to see what could be causing this?
Once you have this information we can dig into why we are seeing so may writes.
Here is a screenshot of that page. I just don’t know why /[slug] is having 586 ISR writes in the last 12 hours but 394 reads. This is the code for that page.
Vercel Staff
Hi , thanks for sharing. Let me dig into this.
Vercel Staff
Hi , how many pages are there under the /slug route based on the Sanity data you have? Also, how often is the underlying data changing?
We have 642 articles that would fall under /[slug]. An article is maybe published 2-3 times a week… Some weeks it’s more and some weeks it’s less.
Vercel Staff
I see. I get that new articles are published 2-3 times a week but what about changes to the existing ones? I'm still working on this one. It's quite an interesting case.
Oh once an article is published it’s rarely re-published. There might be the one offs that maybe a capitalization was missed or something like that, but that’s few and far between. There are more publishing or articles than there is updating old articles. The only thing I can think of off the top of my head is using Sanity Live and how it revalidates a tag when new content is being published, if the newly published thing impacts a page. But I’m also not publishing that many things in our studio to hit 700+ writes every 12 hours.
Vercel Staff
Got it. This information will help me debug as well. My initial hunch is also about Sanity Live because otherwise you have 1 week of caching, so even if all pages are generated in 12 hours, they shouldn't be generated in the next 12 hours.
reply to #10
Vercel Staff
I think we've gone this path before but have you tried time-based-revalidation in Sanity? This should give us some answers. Try setting it to the same value as your page (1 week). If this works, then we can use path based revalidation to add more granular support for page refreshes.
Except there’s an issue with that right? Lets say I set it to export const revalidate = 604800, that would mean any new posts wouldn’t be fetched and no updates to articles would be captured until a week later right?
reply to #12
I also noticed that I am still having a 50% cache miss every 12 hours which is odd
reply to #12
So I thought maybe it was the new Date().getFullYear() that was in my /[slug] json+ld markup or in the footer of the entire website. However, I hardcoded the values 45 minutes ago in production and I am still getting 7x more writes than reads for ISR in the past hour. There hasn’t been any new articles published in this time frame, the last one was 2 hours ago at the time of writing this comment.
It’s like every single article that gets hit is updating the ISR cache for some reason
In the case of the screenshot, it’s updating the cache for an article that was published 3 years ago. Published at 2022-11-22T16:58:45.679Z and last updated 2025-07-16T20:12:33Z.
reply to #13
Vercel Staff
I have a hunch based on Sanity Live docs that it is doing a revalidation. Also, here you can learn about the tags based revalidation and my assumption is there is a change in data that's revalidating all posts.
We can confirm that if you put time based or tags based revalidation in SanityFetch like I suggested. I think it's worth giving a try at least.
So sanityFetch is already adding tags for revalidating on their end, so I shouldn’t need to add them on my end. Furthermore, I tried adding cache: ‘force-cache’ in the client but that then completely disables the revalidating it looks like as after I deployed that change I made an update to an article and it didn’t update at all. The tag revalidation doc you linked to is if you’re still using the old way of revalidating which is using webhooks and a Next.js API endpoint. However, with defineLive you don’t need that now.
Vercel Staff
I see. About the image you shared: is this Sanity revalidating multiple tags in one page invocation? Have you confirmed what these tags belong to?
Yea so those 5 revalidated tag: calls are from a single post publish. It kind of makes sense when you consider this page is on it’s own page, it’s on the home page, it’s on /college/news, /college/football/news, /college/football/news/fcs, and /college/football/news/fcs/big-sky-conference. All of those pages make a request for posts, so when one is published or updated in this case, they have to be revalidated. It would be awesome if it was smarter and just revalidated the actual page itself, but that’s outside of Next.js
As for confirming what these tags belong to, is there a way to see that in Vercel somewhere? These ones are generated on Sanity’s backend which I don’t have any visibility into that I know of.
reply to #18
What’s kind of odd is that I went back and looked for the past 12 hours this morning and even though I had pretty much all cache hits, I was still seeing 1.3k ISR writes in the last 12 hours. In the last 12 hours none of my writers were active on the website, a new version of the website was published 13 hours ago. I removed all the possible new Date() usage in my /[slug] route by hardcoding the values. So there ideally should have been no ISR writes happening if I am understanding the docs. However, there were 1.3k writes during those 12 hours… I don’t even have 1,300 articles on the site, I have about half that. Unfortunately I can’t see the actual paths on the free tier, but this is basically suggesting that each article is being re-written twice every 12 hours.
What’s even weirder now, is that an hour after the last screenshot, and we have way more ISR reads than writes, which is good. But honestly just makes me even more confused.
reply to #19
Vercel Staff
I see. Thanks for explaining. I think Next.js and Vercel have no visibility on the tags' source either. So, it's all Sanity black magic.
Vercel Staff
It seems like your usage is down. Let's monitor and see what went down here. It'd be a great learning if it all comes down to new Date().
I don’t know if it is, that change went in 2 days ago on 9/17 at like 10 a.m. and then the add force cache and remove it also happened on 9/17, just in the evening.
reply to #23
So we are back at more writes than reads in the last 12 hours
To give some context on the changes that have happened in the last 12 hours:
An article published at 2025-09-19T13:06:00.000Z and then republished (updated) at 2025-09-19T17:38:43Z as well as at 2025-09-19T17:17:22.802Z and then republished (updated) at 2025-09-19T17:41:55Z
A deploy at Sep 19, 2025 at 11:02:49 AM MST
Ever since then, writes have been climbing. It looks like there was a spike around11:05-11:10 a.m., so I guess corresponds with the new deploy as the deploy took about 2-3 minutes.
Adding another screenshot of the last 12 hours where I have 1k writes and 270 reads. In the last 12 hours for the website, there has been 2 deploys to production, one 10 hours ago and one 3 minutes ago (since writing this). There hasn’t been a new article published since the ones mentioned in the above response, so 2025-09-19T17:38:43Z being the latest an article was published today.
Vercel Staff
Hi , thanks for sharing all the new findings. Deploys can definitely increase cache writes because every new deploy has a new cache to avoid stale data.
I'm still of the belief that SanityFetch has to do a lot with this because the last time I tested ISR stuff with local data and deployed to Vercel. Everything worked as expected.
Did you get a chance to use the time based caching in SanityFetch? I see no harm in trying.
Hi ,
No I have not tried the time based caching because my assumption is it’s going to be the same as force-cache where no updates happen until that time is up. That would mean the home page would not get the latest article, that would also mean that none of our article grouping landing pages would get updated etc. So that would be the harm is no updates happen on the site. The reasoning is the sanityFetch coming off defineLive does not accept a revalidate, this is what it accepts
query: QueryString
params?: QueryParams |Promise<QueryParams>
tags?:string[]
perspective?: Exclude<ClientPerspective,'raw'>
stega?:boolean
/**
* @deprecated use `requestTag` instead
*/
tag?:never
requestTag?:string
The sanityFetch you see accepting revalidate in the next-sanity README is a custom function you have to build if you’re not using next-sanity/live.
Out of 4.3k calls to Sanity, 4k of them are cached within 12 hours during the day.
I mean even looking at my edge caching, less than half of the /[slug] pages, are hitting edge cache and that’s overnight while nothing is happening on the site.
{
source:'/((?!api/).*)',
headers:[
{
key:'Cache-Control',
value:'max-age=0, s-maxage=86400',
},
],
},
This is what I have in my next.config.ts, not sure if it’s important but
Vercel Staff
Hi , I see. I'm wondering what else could be the reason because writes should be same but invalidation is too frequent. Is there a way to disable the Sanity tags revalidation and use a custom path based revalidation approach? Did you try any other changes?
Ok so I don’t think it’s the sanityFetch from next-sanity/live that is causing it. Apparently someone needs to be VISITING the actual page that is getting revalidated via tags to even kick off its revalidation. I confirmed this multiple times where I published an update to an article, observed no logs in Vercel, visited the URL, observed the change had not be made.
Then while sitting on that URL, I made another change to the article and published it. This time I noticed logs in Vercel for the SanityLive revalidate tag. After refreshing the change from before and the new change were there.
In the screenshot above, you can see my first visit. It causes an ISR cache update (for some reason, I have no idea), then two subsequent visits which were me refreshing to see if the change had somehow been made. Then you see the POST with <SanityLive /> revalidated tag: sanity:s1:jj/CuA which actually ended up revalidating 4 separate tags, then you see my next visit which once again resulted in an ISR Cache update.
I then visited it again while writing this and one results in a 304 and another results in a 200. Both were cache hits so not sure why one was a 304 and one a 200.
Vercel Staff
Hi , about the 304 and 200, I've asked the team. It looks weird because both were edge cache hit. Now I've a theory, do you have queries at layout.tsx level that depend on revalidateTag invocations? For example, when a page is created or edited? If that action causes a query revalidation on the layout level then all pages under the layout will need a revalidation, which might be the cascade ISR writes we see.
What do you think?
So not specifically in the layout.tsx. However, I have some queries that are in the Navbar component that would end up getting revalidated and that is in the layout.tsx, would that trickle up and potentially fall under what you’re mentioning? The query in question basically grabs any new sport and division and conference that gets written about.
reply to #32
client.fetch(
globalNavigationQuery,
{},
{
cache:'force-cache',
next:{
revalidate:604800,
},
},
),
client.fetch(
queryGlobalSeoSettings,
{},
{
cache:'force-cache',
next:{
revalidate:604800,
},
},
)
So I just pushed a change to the navbar to do this instead of using sanityFetch to see if that helps at all.
reply to #32
Well unfortunately, I don’t think that was it. The change went out over an hour ago and we are at a 77% cache miss in the last hour with 16x more writes than reads.
reply to #32
Ok I am a little confused on when an ISR update/write happens. I just saw a request come in for /socon-hoops-1990s-decade-of-glory-mocs-are-born-winners. However, that page has not been touched in forever. But something happened to trigger it to be updated
Vercel Staff
Hi James, thanks for sharing the update. About the last request you shared: we don't keep logs beyond 1 hour for hobby customers, so it's hard to look back and see what could have caused it.
I'm reaching out to other support staff in our team, who may have more experience with ISR in SanityLive. Hopefully we are able to help some insights.
reply to #37
Thanks! I also have log drains setup for this project to Axiom. I think they were grandfathered in due to Vercel removing them from the free tier after allowing them.
Vercel Staff
That's nice. We can check there as well for some specific routes, so let's say /something, how many times it is being served from ISR cache vs not. Is a good starting point.
Anything in particular I should look for in the logs?
Vercel Staff
Hi , I think we can look for patterns. For example, we pick a popular page that has frequent visits, enough to find the cache logs and revalidation.
Say you redeployed today at 9 am, the cache resets here, from then onwards we see if this page was revalidated without any reason, and see how many times was it cache HIT vs cache MISS.
I'm not sure if the Axiom logs have it but that's one way I can see narrowing down.
So I am unable to see the paths under observability in order to see what paths are being hit. Our last production deploy was 2 days ago, we started to implement a canary/development branch that we would merge our changes into and then when enough changes have been merged in, we would then merge that into main.
So seeing as our last production deploy was 2 days ago, Oct 4, 2025 at 8:35:23 AM MST to be exact… We are seeing 460 writes and 273 reads for /[slug] page. Now in the last 2 days, we have had 2 articles published, both yesterday actually at the same time, well maybe like 20 minutes apart. I took a look at a random path /2024-2025-dii-head-coaching-changes which hasn’t had a change since 6/25/2025 and looking at Axiom, it has requests for this article almost daily.
However, looking at the two GET requests for today, the request.vercelCache is MISS for both of them.
Those two GET requests were made 7 hours apart from each other… Looking at the log output from Vercel it says the ISR cache was updated.
Vercel Staff
Hi , thanks for digging deeper into the logs. Is there a way to figure out if the article you chose was revalidated?
I mean even if it was, it should have been a HIT the second time. This one is really blowing my brains right now. Can we do another experiment? How about we set repeat calls to this route and see logs as to when it goes from MISS -> HIT -> MISS. Maybe we can also preserve the response somewhere so we can diff and compare what changed between the MISS and HIT or vice versa.
reply to #29
I’m just another customer but I’ve experimented a lot with NextJS + Vercel’s caching across many different projects. Unless you find yourself in exceptional circumstance, you should not need to specify your own response headers in next.config.ts for caching and it is quite likely this is causing some unexpected behaviour. You should be able to defer to ISR for caching headers: the NextJS features that interact with caching (e.g: ISR) are using the Cache-Control header which you are overriding.
Replace force-static with auto (or delete dynamic as auto is the default)
After that, you will be able to better observe the native Vercel + NextJS behaviour and then go on to rule in/out other potential issues.
The recommendation is a little ambiguous. They’re referring to the exact new Date() call (without a constructor parameter) which creates a new Date instance with the current time – which is different on every instantiation. If you use new Date() in your code then every time your Function is executed, it will have different content. So, new Date(publishedAt) is fine but new Date() is not.
You can rule the issue of changing content in/out by making multiple requests to your page locally and then comparing the output, i.e you want to see identical HTML every time you make a request:
If requests all return the same HTML then the next step is to look at your data source, and determine whether it is returning content that changes more often than you expect. For example, if you had an author’s bio that included their number of articles written, that would change every time a new article is written, causing every page to be invalidated.
Looking at your page, the “You Might Also Like” section seems like it could be an issue. The section likely changes often for many articles because it will bias towards including the most recent posts. i.e: you publish a new post, and then “You Might Also Like” on every related article will need to be updated to show your new article. If I visit a post from October 1st Omar-Rashon Borja’s 2025 Week Six Bowl Projections and scroll down to “You Might Also Like” I can see a post from October 6th is listed, so even if that October 1st post has been unchanged, the would have triggered an ISR write on October 6th.
Another possible cause is the article.authors are being listed in the order they appear in the array, no sorting. If the source (Sanity) doesn’t return them in a consistent order each time then that could be causing the page to change every time – you may want to sort them alphabetically before outputting. I think that’s much less likely an issue, but it’s plausible, so it is best to always sort arrays.
Interesting... So I made these changes... setting dynamic = 'auto' in my /[slug] and then removing the cache control in my next.config.ts. Now it looks like all calls to /[slug] are coming through as edge requests
and not ISR
So now every single call is a function call instead of being static.
Great! That’s progress. Setting dynamic to auto tells NextJS to determine whether the page is dynamic or not, and in this case, your page is being determined to be dynamic. That’s expected, but helpful to have confirmation things are working as expected. You can now set dynamic to force-static again and you should observe the Edge Request count to remain as-is, the cache percentage to go up and the ISR request count to go down.
Interesting... I haven't implemented going back to force-static yet. However, I now have no ISR reads/writes for /[slug], so waiting on the cycle to update for Vercel usage. I am still a little puzzled that none of the edge requests are cached though.
That is correct, because your requests are dynamic: ISR is not enabled.
That is the correct behaviour too, the Edge will not cache the response to a dynamic request (unless explicitly instructed to).
The next step, setting dynamic to force-static again, will enable ISR which includes both the caching headers that are used by the Edge, and the ISR cache. So, by turning on ISR again, you’ll gain both Edge request caching and ISR caching.
Recapping: when you started this thread, you had ISR enabled but you had also set cache-control response headers which were overriding ISR’s cache-control headers causing unexpected behaviour. The cache-control headers prevented the Edge from correctly caching your ISR responses, which in turn caused a larger than expected volume of ISR reads and writes. You have now removed your cache-control header and turned off ISR (by setting dynamic to auto) to get back to the baseline, where all of your requests are being served by your Function – no edge cache, no ISR.
The next step, enabling ISR (by changing dynamic to force-static) will unlock the benefits of ISR (Edge caching, ISR cache). You will see the number of Edge requests remain stable but the cached percentage will shoot up. You will also see the ISR cache reads and writes increase but the number should be predictable, it should correlate with the number of unique pages you have and how often you revalidate.
edit: I just re-read my previous post and it is unclear, I see the confusion. I used clumsy language. I should have said something more like this:
So looks like this was the solution. I still have edge requests but they are all cached now whereas before they were not
And then I have ISR reads and no ISR writes in the last 12 hours
Whelp I may have spoken too soon. In the last 12 hours I have 300+ writes and 400+ reads. However, the last production deployment was 10/21 and no articles have been published since 10/21 either.
This topic is closed. It no longer accepts new replies.