I have a website that has over 600 articles. While investigating some huge usage spikes with a member of the Vercel team we noticed that articles were hitting the cache 8% of the time within 24 hours. Now originally I did have ppr enabled but I was informed that was causing the HUGE spike in fast origin transfer. I have since turned that off but that shouldn’t be the reason why /[slug] routes aren’t hitting the cache more than 15% of the time within 12 hours.
Now I do not have generateStaticParams() as doing that for all pages increases my build time from 2 minutes to over 8 minutes. Now I understand why because it has to fetch the data for everything page and render it. I also use react-tweet with redis to cache calls to X/Twitter which causes any page using that unable to be rendered statically.
What is the typical move here? Is it returning a generateStaticParams() with an empty array which will generate pages on-demand? Or is it just picking an arbitrary number of articles to render, say the last 50? What happens to the rest of the 550+ articles when they get visited if I go that route? Do they get generated on demand or are they all dynamic?
To give some more context, currently when running pnpm build for the site, this is the output for /[slug]
web:build: ├ ƒ /[slug] 4.83 kB 254 kB
When I do generateStaticParams() for every single article I get
What are the pros and cons to having things SSF vs dynamic? I use Sanity to power the site along with their SanityLive component to have live updates so I’m not sure if going SSG would impact that ability or not.
I’d recommend following this guide Guides: ISR | Next.js because the first example is precisely your use case. Try experimenting with different amount for revalidate to see which one fits best with your use case. Higher the number, better the cache hit.
I think rendering the top 50 most visited pages during build should be a good middle ground and then rest of the pages are rendered on-demand by Next.js using dynamicParams set to true.
I haven’t used SanityLive but if it’s a client component then it shouldn’t have issues for the stale data.
I don’t know if setting export const revalidate = 3600 or some number there would be ideal as ideally, articles don’t need to be revalidated after they are published unless it’s republished due to an update in the article. So putting a time there would just mean everything gets revalidated eventually even if it doesn’t need to.
Rendering the Top 50 most visited pages during build would probably work, I would need a way to figure that out as Sanity does not give that data nor is there a way from Next.js. Another alternative could be doing the latest 50.
I believe this is the SanityLive component and I would assume it’s a client component since it uses hooks. They recommend that it’s rendered at the root layout.tsx.
Also I updated from building all 600+ pages to building only the last 50 and set dynamicParams = true and the build time only decreased from 8 minutes to 5 minutes. Not the biggest drop but I guess it’s something.
I also wanted to add that after adding generateStaticParams() and export const dynamicParams = true to my /[slug] route that my home page has been aggressively cached and no longer gets updated from SanityLive. However, the routes built via generateStaticParams() are getting updated.
Also, I thought setting export const dynamicParams = true would then cache it if it’s been hit once and was not originally in the generateStaticParams() but I think that thought was wrong. How can I make sure that those are also eventually cached?
I noticed a URL outside the last 50 got visited so I decided to visit it to see if it would be cached and looks like it’s still running function invocations
Hi @jamesrsingleton, thanks for writing in detail about your testing. Let’s keep the SanityLive feature aside for the moment and first clear out the ISR part.
So, I recreated a simple example repo where I created 4 /blog routes with different generation and caching modes. I was able to get pretty good cache hits as you can see the image below. It will definitely be lesser for a real world scenario.
In my experience, pages rendered during build time are always cached and didn’t lead to function invocation. Whereas the pages built on demand are also generated only once and then served from the Edge Cache. ff in the path means only first fifty pages are rendered during build.
Only time I saw function invocation was when using revalidate (ISR) and cache expired. You can see the 304 STALE response and others where the cache is hit first and then function invocation happens.
so I must not be doing something correctly in my [slug]/page.tsx. Do I need to add the export const revalidate = 600; as well in order for the pages to be cached once they are generated? Also that 600 would only be 10 minutes whereas your comment says to Revalidate every year. If I put export const revalidate = 31,557,600 that should cache it for a year UNLESS SanityLive comes in and revalidates the path/tag I think.
I think the page setup looks good. All pre-rendered pages are cached and it seems like you are building all the pages in this setup so they all should be cached.
I don’t think you need revalidate for caching. In my example repo, I had 4 combinations:
pre-render all: always served from ISR cache
pre-render all with revalidate: always served from ISR cache but revalidated in background after 10 minutes
pre-render first 50: first 50 are served from ISR cache, rest are generated on demand and then served from cache
pre-render first 50 with revalidate: first 50 are served from ISR cache, rest are generated on demand and then served from cache but revalidated in background after 10 minutes
For your use case we should be doing #1 or #3 set up.
You can use revalidate to explicitly cache the content in Edge cache for an year if you like but I don’t see the need.
And it does not seem to be hitting the cache? It looks like everything is being hit as if it’s the first time. This article is from 2024 so it isn’t going to be in the latest 50 articles. However, I would expect it to be cached?
And the route seems to be getting hit MULTIPLE times for some reason, and I doubt people are visiting a year old article. Looking at my analytics I am the only one who has visited it this week.
Here is another case where I just published this article and shared it to multiple social media platforms. The route was hit 29 times and every single one of them is a function invocation instead of prerendered.
Hi @jamesrsingleton, thanks for sharing these additional information.
Home page and other static routes are not shown in logs (after the first visit to that deployment) because they’re served from Edge Cache. I’m discussing this with the team because I also noticed this behavior and had some feedback.
Now coming back to the other routes:
what happens when you visit any of the latest 50 pages: are they cached or not?
what happens when you use the revalidate = 31_557_600?
Update:
Upon more research I found that Sanity docs
suggest to keep the stega: false for static generations.
@jamesrsingleton I see. It’s weird. I think it could be due to the revalidate option of sanityFetch, which defaults to 60. Can you try setting that number to a very high value, like one year?
So I even added export const revalidate = 604800 to cache for a week because I thought that would then cache pages that were not in the generateStaticParams() after they were visited. However, I am not seeing that.
Hi @jamesrsingleton, I see. Can please share your public repo or a minimal reproducible example. That will let us all work together from the same code to figure out what’s going wrong.
So, a sample project with sanity setup (throwaway project) like yours. This way I can test and validate myself and then share with you. Otherwise the feedback loop is too long.
So I ended up having to add export const dynamic = 'force-static' to my /[slug] page to get things to then statically render after being visited. However, it does say from Edge Cache vs ISR Cache and I honestly don’t know the difference.
Hi @jamesrsingleton, thanks for updating here what worked for you. Edge cache is used for static assets, since you’ve set force-static there will be no ISR in place. In terms of caching they both are same ie you will get a cached response.