​​Editor’s note: This article was updated on 12 April 2022 to address beta support for ISR in the latest version of Next.js.
Next.js is a multi-page React framework bundled with all the features you need for web application production — it is one of the leading platforms for building a React application.
Traditionally, two methods for serving have been prevalent: static site generation (SSG) and server-side rendering (SSR). Next.js v9.5 introduced a new strategy called Incremental Static Regeneration (ISR), a hybrid version of the two.
In Next.js 12, we have another feature called on-demand ISR, where we can revalidate a given page on demand, ignoring the remaining time left for the next revalidation — this is something that has long been requested by developers and we will explore its use in this article.
This post will cover the relevant bits of this new strategy and explain why it’s a game-changer for web development.
The difference between SSG and SSR is when the page’s HTML is generated.
When using SSG, the HTML is generated at build time. SSG pre-rendering makes it easy to cache and fast to deliver.
The term “static” comes from the fact that the HTML is static, but it doesn’t necessarily mean that the page itself is static. You can still fetch data and create dynamic interactions using client-side JavaScript.
According to the Next.js team, SSG is the recommended way to proceed. It should be the default choice as long as it fits. Some of the SSG use cases may include a blog, portfolio website, ecommerce application, or documentation website.
On the other hand, SSR generates the page’s HTML on each request. It is much more flexible than SSG because you can change the HTML without building the application every time. So, if you have data that is continuously updated, SSG might not be a good fit.
Imagine serving your Twitter homepage as a static page. Sounds strange, doesn’t it? This flexibility has a significant impact on performance and makes it much harder to cache.
SSR is the way to go whenever SSG is not the right fit for you.
ISR is a newly released feature that allows the regeneration of static pages during runtime. It’s a hybrid solution of SSG and SSR.
The page is generated on the first request. Unlike in SSR, where the visitor has to wait for the data fetching, a fallback page is served immediately.
During the fallback stage, we can present placeholders and a skeleton page until everything is resolved. Skeleton pages are a common pattern that you can see almost everywhere.
Once the data is resolved, the final page is cached, and subsequent visitors will receive the cached version immediately, just like with SSG. We can also set when Next.js should revalidate the page and update it.
Even when revalidating, the visitor first receives the cached version and only then the updated version.
This caching strategy is commonly known as “stale-while-revalidate.” Of course, it was possible to achieve even before, but ISR has further democratized this ability and made it more accessible.
Evolving your SSR pages to ISR can significantly increase your application’s performance and improve your Lighthouse score by tens of points — as well as delighting your visitors with a faster experience.
Next.js makes it a breeze to switch between SSR and SSG (and ISR). Every page should export a function that gets the required properties to render the page.
The React component of the page does not even know where the properties came from. Let’s see an example for SSG and SSR, taken from Next.js documentation.
Here’s an SSG example:
export async function getStaticProps() { const res = await fetch('https://...'); const data = await res.json(); return { props: { data } }; }
Every static page should export a getStaticProps
function. The function is invoked at build time, and the returned props
are forwarded to the React component as props
. In this example, we call our remote API endpoint and return its response.
And now, here’s the equivalent SSR example:
export async function getServerSideProps() { const res = await fetch('https://...'); const data = await res.json(); return { props: { data } }; }
The only difference is the function name. There’s also another fundamental difference, and it’s that this time, the function is called on every request, rather than just at build time.
Using ISR is much like SSG with an additional revalidate
property. This new property states how often to revalidate the page (in seconds):
export async function getStaticProps() { const res = await fetch('https://...'); const data = await res.json(); return { props: { data }, revalidate: 60 }; }
This is how simple it is — nothing fancy!
Detecting fallback mode is as easy as using React Hooks. The Next.js router holds this information for us. All we have to do is to get the router and extract this information, as follows:
const { isFallback } = useRouter();
isFallback
is a boolean that automatically updates when the fallback mode changes. You can take this snippet and put it in every component you would like.
The main reason for recommending SSG over anything else is to reduce the time for the first paint and the blocking time (measured in lighthouse), thus providing a better user experience for your visitors.
Once you go down the SSR path, you increase the blocking time and, as a result, the first paint. This is because the server has to fetch some data and then generate the HTML on the fly before sending any HTML back to the client.
To deliver the best experience, we have to choose carefully what data to fetch on the server-side.
My rule of thumb is to fetch data that is required for meta tags only.
Correct meta tags can affect the link’s appearance when someone shares it on social media, for example, and they can also improve your SEO. You don’t want the search bots to rely on JavaScript to index you correctly — make it easy for them to scrape and understand your website.
ISR is a bit different, but my general rule of thumb still holds.
When using ISR, the first paint and the blocking time might not be affected due to the fallback stage.
Next.js immediately sends HTML back even before fetching the server-side data. Depending on the design of your page, the server-side data fetching might delay the first meaningful paint.
Before wrapping up, here’s a working example that puts into practice everything we learned. Our demo will randomly pick a public API using this awesome API in three ways: ISR, SSG, and SSR.
The code is available on GitHub if you want to try it out yourself.
Let’s start by building our page component. Thanks to Next.js, we can write it once and use it for all methods as long as we provide the same props:
import { useRouter } from 'next/router'; export default function Page(props) { const { isFallback } = useRouter(); if (isFallback) { return <></>; } return <div> <h1>{props.name}</h1> <p>{props.description}</p> </div> }
Our props object contains the name of the public API and its description. We display them using a header and a paragraph element accordingly. One caveat is that we have to check for the ISR fallback mode, as mentioned above.
During this mode, the props might not be defined, so we have to take care of it. I return here an empty element for simplicity’s sake, but a better approach is to show loaders or a skeleton page.
Our page is ready, and now we can fetch some data from the API. We will use node-fetch for this purpose, as all the calls are made outside the browser, so the browser’s inbuilt fetch
function is not available.
import fetch from 'node-fetch'; export async function getRandomAPI() { const res = await fetch('https://api.publicapis.org/random'); const json = await res.json(); return { name: json.entries[0].API, description: json.entries[0].Description, }; }
We call the API endpoint that returns a random public API and organize it into a better structure that includes the name and description, just like our props object.
The essential elements are ready, and all that’s left to do is create the actual Next.js pages — one for every rendering method.
SSR:
import Page from '../Page'; import { getRandomAPI } from '../publicApis'; export default Page; export async function getServerSideProps() { const props = await getRandomAPI(); return { props }; }
SSG:
import Page from '../Page'; import { getRandomAPI } from '../publicApis'; export default Page; export async function getStaticProps() { const props = await getRandomAPI(); return { props }; }
ISR:
import Page from '../Page'; import { getRandomAPI } from '../publicApis'; export default Page; export async function getStaticProps() { const props = await getRandomAPI(); return { props, revalidate: 30 }; }
You can see that all of these implementations look similar.
The SSR version uses getServerSideProps
instead of getStaticProps
. The ISR version sets the revalidation period to 30s
. Pretty easy, right?
You can see it in action here. Note that the SSR version will randomly pick an API on every request. The SSG version will pick one randomly at build time. Lastly, the ISR version will refresh the selected API every 30 seconds.
You can also use on-demand ISR, which is introduced in Next.js 12 as a beta feature. This means that you can trigger a revalidation through an API route, ignoring the remaining revalidation time:
export default async function handler(req, res) { await res.unstable_revalidate('/path-to-revalidate') }
Now you should have a sense of understanding about the nuances of SSG, SSR, and ISR. If you optimize for performance, you should first consider using SSG, followed by ISR, and — as a last resort — SSR.
Both SSG and ISR have some limitations, and that’s precisely why they might not fit every use case as well as SSR. We also covered some of the performance aspects between the three.
My rule of thumb is to server-side fetch only data required for the page’s meta tags. Happy coding!
Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your Next.js apps — start monitoring for free.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
6 Replies to "Incremental Static Regeneration with Next.js"
Awesome content explaining ISR! Thank you!
🤯 awesome explanation.
Thank you so much!! Well explained.
Hi, I tried SSG with revalidation, but it fires the api request always. It’s a little hard to debug because the network tab doesn’t shows a request, but logging my api, the request is orchestrated. Maybe there is something I’d missed
hello. thank you it was very usefull.
I have a question please.
For example with a website handling a very high number of small pages who displays always the same content, like a huge ecommerce website with 10 millions let’s be crazy, what would you use between ISR, SSG, and SSR ? in the code bellow it’s a fetch query for one page only, but how to fire all the pages rendering for exemple in SSR ?
We cannot make a query to fetch so much data in one shoot or API/database will crash or timeout.
Thank you
Thanks for the great article.
somewhere u said ‘returned props are forwarded to the React component as props.’ i think it is forwarded to page component not to the react component.