The Ultimate Guide To Incremental Static Regeneration ( ISR ) in Next.js

Incremental Static Regeneration ( ISR ) is relatively a new feature in Next.js. It is immensely helpful for a developer, and greatly improves the performance of web applications. You need to have an understanding of getStaticProps and getstaticPaths to have a clear idea of ISR.

Why do we need Incremental Static Regeneration?

  • With SSG, the web apps are pre-rerendered at the build-time. One issue is that as the number of pages increases, the build time also increases.
  • Stale data
    • For web applications with dynamic data, SSG does not help because it does not reflect the updates in data sources.
    • Even if you use dynamic pages with getStaticpaths and set the fallback property to true or ‘blocking’ in Next.js, it will not resolve the issue. Because pre-rendered pages on the initial request are delivered for the subsequent request. These pre-rendered pages do not show any changes made after the initial request and pages being rendered in the background.  

What is the solution?

Now that we know the two main issues with SSG and static sites, you might wonder if there is any solution for these issues.

The most obvious one is to build the entire site with SSG whenever there is a change in data. While this could be the solution for small apps that will not grow over time, this definitely impractical for medium or large sites.

So, a better approach is to update only the pages that depend on dynamic data. But, how can Next.js do this with SSG?

Enter ISR, or Incremental Static Regeneration

Incremental Static Regeneration and revalidate prop

ISR is a technique that resolves the above-mentioned issues with static sites.

Problem: Increase in build time as pages increase & the issues of serving stale data

Solution: statically generate only those pages that use dynamic data. There is no need to regenerate the entire site at the build time if data is updated in the API or any other data source. 

How does Next.js do this? 

It Introduces a property called revalidate to getStaticProps function. 

How to use revalidate prop in getStaticProps?

What does revalidate prop do?

You can specify a time period ( for example 10 seconds ) for this prop so that Next.js will attempt to regenerate the requested page after this time period if a new request is made. Next.js will serve these new pages with updated data to the client.

I believe the best way to understand how getStaticProps works with the revalidate prop is to build a simple application, change data in the data source, and see how the Next.js app behaves as the data changes.

Let’s build an application …

We are going to create a simple web app to understand how the revalidate prop changes the behavior of the getStaticProps function.

  1. Open the terminal and run npx create-next-app demo-next-app
  2. Open the project folder in your favorite editor 
  3. For this web app, we are going to use JSON Server, which is a fake REST API. We will be using this on the localhost.  
  4. In the terminal run, npm install json-server. After the installation is completed, you will notice it is listed as a dependency in the packag.json.
  5. In the root folder of the project, create a file name db.json, and add the following JSON object.

db.json

{
    "products": [
      { "id": 1, "title": "Laptops", "price": "800" , "description":"This is a Laptop" },
      { "id": 2, "title": "Desktop", "price": "750" , "description":"This is a Desktop"}, 
      { "id": 3, "title": "Tablets", "price": "505" , "description": "This is a tablet"}
    ]
}
  1. Now, add  “server”:”json-server –watch db.json –port 4000″ to “scripts” in package.json. You can run the server with npm run server. You need to keep it running in the development mode and also when you build and run the application later to do your experiments with new data.

scripts added to package.json
  1. Create a folder named “products” in the pages folder.
  2. Inside the “products” folder, create a file: index.js, and add the code below to it. The index.js will show the list of products in db.json on the route localhost:3000/products.
import Link from 'next/link';
 
function ProductList( { products }){
    return(
        <div>
            <h1>Our Products</h1>
            {
                products.map(
                    product =><div key={ product.id }>                       
                        <Link href= { `products/${product.id}`}>
                            <li><a>{ product.title }</a></li>
                        </Link></div>                 
                 )
            }
        </div>
    )
}
 
export default ProductList 
 
export async function getStaticProps(){
        const response = await fetch('http://localhost:4000/products');
        const data = await response.json();
        return {
            props:{
                products: data,
            },
		 
        }
}
  1. Now, inside the same folder( product folder), create another file called [productId].js and the following code. As you can see, this is a dynamic route.
export default function Product( { product }){
   
    return(
        <>
            <h1>Name : { product.title }</h1>
            {
                 
             <div>
                <p><h2>Description</h2><br />{ product.description }</p>
                <p>$ { product.price }</p>
             </div>
                 
            }
        </>
    )
}
 
export function getStaticPaths(){
        return {
            paths:[{ params:{ productId: '1' }  },],
            fallback:'blocking'
        } 
}
 
 
 
export async function getStaticProps( context ){
     const { params } = context;
     const response = await fetch(`http://localhost:4000/products/${params.productId }`);
     const data = await response.json();
 
    return {
        props:{ product: data, },
        revalidate:10,
    }
}
  1. When a user clicks on a name of a product on the product page, the details about the particular product display on the route localhost:3000/products/productId. productId in the URL is dynamic.

Build and Run the app

In order to see the behavior of getStaticProp with revalidate prop, you must build the application with npm run build.

Remember when you run the build command, you need to keep the server running in the background on loaclhost:4000.

After having built the application, you will see details on the terminal. As you can see ISR is in the summary. Now, you can run the application with npm start.

Incremental Static Regeneration in the summary in the terminal

How getStaticProps behave with revalidate prop 

On the [productId].js , there is a getStaticProps function with revalidate prop  set 10, which is in seconds. 

export async function getStaticProps( context ){
     const { params } = context;
     const response = await fetch(`http://localhost:4000/products/${params.productId }`);
     const data = await response.json();
 
    return {
        props:{ product: data, },
        Revalidate:10, //10 seconds
 
    }
}

Let’s how this works.  Have a look at the following scenario.  Simulate this scenario by changing the data in db.json.

How Incremental Static Regeneration  works

Steps at each time 

  1. At 0 seconds: 
    1. the initial request 
    2. The user is served with pre-rendered static files. For example, these files could be in a CDN
  1. At 5 seconds: 
    1. The data in the data source is updated. ( Ex: change in laptop prices )
    2. New request from a user
    3. The user is severed with data in the cache. Therefore, the user sees the old price of the laptop
  1. At 10 seconds: 
    1. New request from a user
    2. Start regenerating new pages in the background, and update the cache 
    3. The user is still served with data in the cache, so, the user still sees the old price of the laptop

Note: if new page generation failed, the cache is not updated. Thus, the user is served stale data in the cache.

  1. Immediately  after the cache is updated completely 
    1. New request from a user
    2. The user is served with web pages that reflect the change in price

A little bit more on revalidate…

As you can see in our example, I have used revalidate prop in the getStaticPorps function in the [productId].js files. However, you can use it also in index.js as well. 

export async function getStaticProps(){
        const response = await fetch('http://localhost:4000/products');
        const data = await response.json();
        return {
            props:{
                products: data,
            },
		 revalidate:10,//you can add this line
 
        }

Please make sure to rebuild the project again as you change the code.

As I explained previously, if you add a new item or change the title of an existing product while the application is running, the changes will appear after 10 seconds on the ‘http:3000/products’ route, which is the new product list.

Last thoughts

I hope this guide helps you understand the concepts behind ISR. If you need more time to do your experiments with data, you can increase the time of the revalidate prop to 20 seconds or 30 seconds. Although ISR provides a solution to deal with the problems with an increase in build time and stale data, it is not a complete solution. There is still a chance that the user is served with stale data. This is where SSR(server side generation ) comes in. I will talk about it in my next post.

Other resources

Next.js official documentation

A Complete Guide To Incremental Static Regeneration (ISR) With Next.js

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *