How we utilised some of the power of next.js to offer increased performance, reduced costs and offer a much-improved customer experience
Anyone familiar with Next.js will know just how powerful it is. It gives you the power to switch between fully static generated pages, server-side rendered pages and the “relatively” new incremental static regeneration depending on the type of site you run or the needs for that particular page or section.
But with this power comes great responsibility
As a website owner or creator, you need to pick the right page generation that matches the needs of your visitors. For example:
SSR: Live Data
- If your content is constantly changing and the underlying data needs to be as “live” as possible or if your site is reactive to user interaction and states
SSG: Infrequent content changes -
For when your site has content that barely changes (think guides/manuals) or has infrequent changes but you really need that extra performance boost
Now there are performance benefits for all of the above too, ranked from SSG > SSR. We won’t go into that here but there are a bunch of sites we’ll link to at the bottom that you can check out later.
Originally our plan was to use SSR for our client’s site, as they had somewhat frequent content changes and didn’t want the hassle (or expense) of rebuilding the content every time it changes. * Traditionally this would be done utilising SSG and webhooks from the CMS to tell your host to deploy every time content is published
But as mentioned above SSR is the least performant of the bunch and for 98% of the content that rarely ever changes it would be overkill to request live data and render on the fly for them.
Enter ISR - Incremental Static Regeneration
ISR is great for when your site has content that can change on a frequent basis (daily/hourly) and you really want to benefit from that sweet sweet static generated performance goodness.
But what’s the downside I hear you ask? Well, not all things that smell sweet are the tastiest and in this case, ISR does have some nasty tasting downsides. Firstly let’s see how ISR works:
When you push a change your host (Vercel/Netlify etc) will create a static version of every page that you have setup (SSG-esque)
Then depending on the revalidation time that you set in your generation options, ISR will show the static generated content first
Then after that period of time elapses it requests the “old” page be invalidated from the cache and a “new” version be generated
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
if (!res.ok) {
throw new Error(`Failed to fetch posts, received status ${res.status}`)
}
return {
props: {
posts,
},
// Revalidate the content every 10 seconds
revalidate: 10,
}
}
What this means is, depending on the time you set above (in this case 10s) a visitor may see the old page content and then the next visitor to that page would see the newer updated content.
If this isn’t a dealbreaker for you then happy days 🕺🏼 . But if your site serves over 100k > 1M visitors what does this revalidate mean to your usage?
Revalidate + many visitors = many requests
That’s right if you have tons of visitors to your site each visit will request a revalidate at most once every 10seconds. This means one visitor can make many requests, which means many visitors will make many many requests 🤯
Depending on the host you use and the type of CMS you are utilising this may not be a problem, but with most headless CMS’ this increase in API calls can seriously add up and potentially cause you to breach fair usage which generally leads to a payment to be made in order to keep your account active.
So ISR is bad?
Well, no, it’s still more performant than SSR but if you’re thinking that it will be a magic cure for what you need then you may be disappointed.
Thankfully, the amazing team at Next.js have been listening to your feedback and as of v12.1.0 they introduced On-Demand Revalidation. They claim this allows you to:
Starting with v12.1.0, Next.js supports on-demand Incremental Static Regeneration to manually purge the Next.js cache for a specific page. This makes it easier to update your site when:
Content from your headless CMS is created or updated
Ecommerce metadata changes (price, description, category, reviews, etc.)
ODR to the rescue
If we break that down, it means that you can benefit from SSG (Static Generated Content) but allow for a single page to be rebuilt on-demand. So if you are running your site from a CMS, like Contentful, you could re-build the one page that has been updated.
When you compare this to the other build methods mentioned above it means:
ODR vs SSG: SSG would require the server to rebuild ALL pages every time content changes, ODR only requires that one page be rebuilt
ODR vs SSR: SSR would require the server to render ALL pages every time they are viewed, ODR only requires pages to be rendered on build time OR when requested for a single page
ODR vs ISR: ISR would require the server to rebuild ALL pages after a set period of time after they were viewed
So basically, ODR gets a bit of benefit from SSG and some from ISR. Win.
Great, but how do you do it?
If you are already using SSG (or ISR) it is thankfully super easy to get this setup:
Update your Next.js to at least V12.1.0
Change your build to getStaticProps
Remove revalidate from your getStaticProps function
Now you are almost ready to start accepting revalidate requests on-demand. If you haven’t already done so we recommend adding a secret token to your Environment Variables, the name isn’t super important but you’ll need to call this from your CMS at some point later.
In order for Next.js to be able to accept this “request for revalidation” you will need to create a new API function:
// pages/api/revalidate.js
export default async function handler(req, res) {
// Contentful sends secrets as a header
const secret = req.headers['secret'];
// Contentful sends additional info in the body, like the page type, id etc
const body = JSON.parse(req.body);
// Check the secret and next parameters
// This secret should only be known to this API route and the CMS
if (secret !== process.env.CMS_SECRET_TOKEN) {
// You can check invalid logs in your host, Vercel/Netlify etc
return res.status(401).json({ message: 'Invalid token' });
}
try {
// Revalidate this page only when this webhook is called
// And yes the way Contentful sends the slug is messy :(
await res.unstable_revalidate(body?.fields?.slug?.['en-US']);
return res.json({ revalidated: true });
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating');
}
}
Next up, add this to your CMS as a webhook. An example of Contentful is below:
Also, remember to setup and pass your secret token otherwise it will not work!
Is that it?
Yep pretty much! So now whenever your site content changes, it will call the revalidate API via a webhook from your headless CMS. That will then rebuild the newly updated page in the background.
From our testing we found that the single page rebuild can take anywhere between 2s - 5s from publishing in Contentful to being rebuilt and accessible on the website 💥
Outcomes
We mentioned at the top of the article that utilising ISR could result in a large number of API calls. By switching to ODR we were able to reduce the number of API calls by 90% and in turn the number of serverless functions was reduced by around the same amount.
This resulted in a cost-saving of around $2-4k per month in hosting and API fees alone 🚀
Depending on your site this could mean massive cost savings for your business, plus it means your site visitors are always receiving the most performant site of the “almost always” latest version.
Caveats
This article wouldn’t be complete without mentioning the caveats...and there are a few:
If your site utilises CMS-controlled components that are accessible on many pages those pages will not be updated
E.g. If you have a content block in Contentful that you have “embedded” on many other pages, if you update that single block then you would need to find out everywhere that block is used and rebuild all of those pages individually 🤯
Next.js are looking to add an option for the API/revalidate function to accept an array of slugs so keep on the lookout for this as that could help solve the above
Stay tuned for a solution we're working on to help solve this, once Next.js rollout an improved way to handle multiple revalidations
Depending on who manages your site, asking a client or content editor to understand when a “single page” rebuild is required vs an “entire site rebuild” can be a big ask.
Factoring in the day-to-day usage of your site is a massive deciding factor in choosing ODR vs ISR
TL: DR;
Next.js’ ODR (On-demand regeneration) is pretty amazing, but it is just the first step in getting your site to be performant, save money and give your visitors the best experience possible.