Learn moreKontent.ai has raised $40 million in growth capital and is now a fully fledged, standalone company!
Petal
How to Improve Cache Efficiency and Reduce Costs with Next.js On-Demand ISR

How to improve cache efficiency and reduce costs with Next.js on-demand ISR

Next.js “On-Demand” Incremental Static Regeneration is here. What is it? How does it work? And how can we use it to deliver content changes to our users sooner whilst reducing unnecessary page renders?

Tom Marshall

Tom MarshallAug 3, 2022

At Kyan, we love Next.js. We love the flexibility to choose between different rendering models for each page route, and we love Incremental Static Regeneration (ISR) for delivering pre-rendered performance on large sites without the lengthy build times.

To date, Next.js’ ISR has relied on time-based caching, i.e. pages are revalidated after a developer specified duration, but Next.js has recently introduced a new revalidation mechanism called “on-demand” ISR.

So what is “on-demand” ISR, how does it work, and will it make time-based ISR a thing of the past?

What is Incremental Static Regeneration?

To understand what on-demand brings to the table, first, we need to define Incremental Static Regeneration. If you’re already familiar with ISR, you can skip this section.

With ISR, we can pre-render selected pages at build time without waiting to pre-render every page. We can then update (revalidate) those pages and add new pages to the cache at runtime after the site is live.

ISR uses a stale-while-revalidate (SWR) caching model so that if a previously rendered response for a request exists, it is returned instantly, regardless of its age. Next.js will then check if that response is stale (has expired based on the prescribed period) and, if so, regenerate the page in the background so that the next request will see the updated content.

Next.js incremental static regeneration
Next.js Incremental Static Regeneration

In short, ISR provides the performance and resilience benefits from the pre-rendered static content, but without the drawback of the lengthy build times for large sites.

What makes Incremental Static Regeneration “On-Demand”?

With traditional ISR, we specify the revalidate duration in seconds for the page route. Each page is cached for that duration, then regenerated by the subsequent request after that period has expired.

With on-demand ISR, Next.js has provided a revalidate() function that we can call programmatically to regenerate pages directly.

This on-demand revalidation can be combined with a conventional revalidate duration or used without one. If no revalidate duration is specified, the page content will only regenerate when revalidate() is called.

How does it work?

The exact implementation will depend on the use case, but a general approach for on-demand revalidation is as follows.

We need to add an API route for revalidation, e.g. pages/api/revalidate.js, and that API route needs to:

  1. Ensure the authenticity and validity of the incoming request.
  2. Identify which page path(s) should be revalidated based on the request.
  3. Call revalidate() on those page path(s).

What calls the API route will depend on the use case, but it would typically be a webhook.

In our example that webhook request comes from Kontent.ai when content changes, but it could be an eCommerce system when product data updates, or any other external system that provides page content.

Next.js On-Demand Incremental Static Regeneration

The code for that pages/api/revalidate.js API route would typically look something like this:

// pages/api/revalidate.js
 
export default async function handler(req, res) {
 // ensure the request is valid, with a secret or
 // other mechanism
 if (!isValidRequest(req)) {
   return res.status(401).json({ message: 'Invalid request' })
 }
 
 try {
   // identify which page path to revalidate, e.g. /blog/foo-post
   const pathToRevalidate = getPathToRevalidate(req)
 
   // revalidate the page
   await res.revalidate(pathToRevalidate)
  
   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).json({ message: 'Error revalidating' })
 }
}
 
const isValidRequest = (req) => { /* TODO: implement */ }
const getPathToRevalidate = (req) => { /* TODO: implement */ }

I’ve created a demo that adds on-demand ISR to the official Next.js Kontent.ai example project to provide a complete working example. You can check out the complete pages/api/revalidate.js implementation and the rest of the project on GitHub: Next.js & Kontent.ai On-Demand Incremental Static Regeneration (ISR) Demo.

What can we use it for?

Consider a news site with tens of thousands of articles dating back over the last decade, all served from the /articles/:slug page route. There’s a long tail of old articles for which the content won’t have changed in years.

If we set a long revalidation duration, e.g. a few hours, the recently published articles that are still being regularly updated won’t show the latest content promptly, which is critical for a news organization.

If we set a short revalidate duration, e.g. a few minutes, then the new articles are kept up to date, but every time an old article is viewed, we unnecessarily regenerate that page with the same content that hasn’t changed in years - unnecessarily upping our costs for hosting and third-party API usage.

This challenge is not unique to news organizations. Any site with page routes containing thousands of pages and a long tail of less popular content, such as the product details route on a large eCommerce site, will face the same issue.

On-demand revalidation allows us to invalidate our cache more efficiently, only when the content has changed and faster, as soon as the change is published.

Is On-Demand a Replacement for Time-Based ISR?

Is this the holy grail of caching, where caches are only invalidated precisely when the underlying content changes? In short, no. At least, not feasibly in most use cases. The main issue here is how content is often reused around a site.

Let’s consider our news organization site again. On-demand ISR provides an excellent solution for revalidating the /article/:slug page route when the corresponding article item is updated in the CMS, but it’s unlikely the content of that given article is isolated to just that page path. It might also appear within;

  • a section on the homepage,
  • on multiple article category/topic pages,
  • on other news articles as related content,
  • etc.

If we’re using on-demand ISR in isolation, our API endpoint would have to identify every page path that features the given news article when it changes.

Also, our news article page likely includes content from different content types, for example, a reference to the author/journalist. What happens when the author’s bio or avatar image is updated? We’d need to handle that as well. What about the news article category? The related tags? The embedded videos?

Assuming a site of any real-world complexity, the logic required to keep the entire site in sync through on-demand ISR alone would be undesirably complex to implement and even worse to maintain.

A Combined Approach

So whilst it might be impractical to use on-demand ISR instead of the traditional time-based ISR, we can use them together.

By introducing on-demand ISR to regenerate the key page(s) when content changes are published, we can update the content on the site sooner, as we don’t need to wait for the remainder of the revalidate duration.

We might choose to revalidate other related pages simultaneously, e.g. the homepage, but with the time-based duration as backup, we don’t have to worry about identifying every page location where the content is used, as the time-based revalidation will ensure eventual consistency.

Also, depending on the nature of the site, with on-demand ISR in place, we might also feel we can extend the time-based revalidation period for specific page routes, reducing our renders and, therefore, our costs.

Conclusion.

Unless the site in question is relatively trivial in scale or complexity, on-demand ISR is unlikely to be a sensible alternative to the traditional time-based ISR.

Instead, it should be viewed as an enhancement to time-based revalidation that enables us to cache more efficiently, deliver updated content to users sooner, and provides the opportunity to reduce unnecessary page renders by upping our revalidate duration(s).

If you’re using on-demand ISR in production, I’d love to hear about your experience. Find me on the Kontent.ai Discord.

--

We are Kyan, a technology agency powered by people.

Tom Marshall
Written by

Tom Marshall

I’m Head of Technology for Kyan, focusing on building technology that changes businesses for the better. I’m a Rubyist at heart, but I’m spending more and more time in the Jamstack space, primarily with Next.js.

More articles from Tom