Understand Next.js's incremental static regeneration against the SSR & SSG

Contents
    Try Storyblok

    Storyblok is the first headless CMS that works for developers & marketers alike.

    In this article, you'll be able to learn the difference between Server Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR), and how to implement ISR into your Storyblok projects.

    If you prefer to watch the talk instead of reading the article, check the recording of Arisa's talk at Next.js Conf 2021.

    Arisa Fukuzaki explains the difference between the ISR, SSR & SSG at Next.js Conf 2021.

    NOTE:

    This blog post content is presented at Next.js Conf 2021. The talk video will be accessible after the conference.

    IMPORTANT:

    Relevant GitHub repository with all the code sample is available here

    Advantage of going headless for such a huge project

    Before we talk about how to integrate Incremental Static Regeneration (ISR) into your Storyblok projects, let's talk about why we should go for a headless approach with ISR.

    Each project could be various scales and bigger scale projects tend to have more challenges to solve. If your projects are distributing their markets on a global scale, the amount of the content tends to be bigger.

    One of the common concerns for such cases is building time.

    Storyblok editing capabilities

    The issue of Static Site Generation.

    The build time will increase followed by the number of pages. The time you need to wait could be from minutes to hours. This is a huge blocker when you need to make changes to something in your content. If there are many editors making changes at the same time, it'll be quite hard to handle all the changes in a queue in the right order.

    To solve the long build time, frameworks have great selections of choices to solve this issue. In order to be able to choose the best frameworks for each project, there should be flexibility to choose the right tech stack.

    The headless approach gives us the flexibility to choose frameworks, libraries, and even languages to enhance the performance. Also, it's one of the popular ways to work with modern frameworks.

    Going headless architecture brings you other benefits. For instance, Storyblok allows users to use a component-based approach, visual editor, advanced role permissions, and many more.

    Pros and cons between SSR, SSG, and ISR

    We saw the advantages of going headless approach. Once your projects are set for a headless approach, it's time to decide on the best tech stack. In order to be able to choose the best technology to have the content available as fast as possible, let's see the comparison between SSR, SSG, and ISR.

    Server Side Rendering (SSR)

    Pros: Pages are always updated whenever being reloaded. Hence, it's quick to have initial access to pages. Good for SEO.

    Cons: Performance will be low and need to deal with caching issues. SSR by default is often slower than SPA and SSG. Also, more API calls are required because the page HTML is generated in each request.

    Static Site Generation (SSG)

    Pros: Fast performance and the security attack on a static website can be minimized. Fewer API calls.

    Cons: Pages are generated at build time. It means until the next build, page content will stay the same. Longer build time with more pages.

    Incremental Static Regeneration (ISR)

    Pros: Possible to choose the number of pages to generate with an adjustable stale time with revalidate. Really fast performance with less build time.

    Cons: There's still stale time in the beginning.

    Server Side Rendering (SSR) was popular in a monolithic world. In a Jamstack world, Static Site Generation (SSG) was applied often. Yet, both approaches had concerns, such as performance, caching, and long build time.

    Incremental Static Regeneration (ISR) was created to find a hybrid and better solution by Next.js.

    The whole concept is that to enable static-generation on a per-page basis without rebuilding the entire site.

    Storyblok editing capabilities

    Idea of Incremental Static Regeneration.

    There's an option to choose the number of pages to generate, so that it keeps the advantages from static-generation, and minimizes the build time.

    If there're a lot of pages on your project, ISR enables you to load only the most recent pages at build-time and more pages are cached ahead of a user's request.

    The diagram above is describing the loading. In logic, ISR works as the diagram below explains.

    Storyblok editing capabilities

    How the Incremental Static Regeneration works.

    Let's say, there are two users visiting a common page, user A and user B.

    When user A visits a page at 0 seconds from the diagram, user A sees the stale version of the page. When user B visits the same page after 60 seconds at the lower row, user B sees the updated version of the page.

    ISR triggers to generate a page when user A visits the stale version of the page. That's why user B who visited after 60 seconds was able to see the updated version of the page.

    Needless to say, it's possible to change the time length by developers. Based on what we took a look at together how ISR from Next.js works in logic, let's move on to see how we can integrate it into a source code level.

    HINT:

    We recommend you take a look at our 5 minutes Next.js integration tutorial to have a better understanding of how ISR works at Storyblok projects.

    Create [[...slug]].js file

    At Next.js, the root or home entry will be created in pages directory. Create pages/[[...slug]].js file.

    If you would like to compare SSR, SSG, and ISR in one repository, there is a way to create each directory for all three systems. You can take a look at the [src/pages directory](https://github.com/storyblok/storyblok-nextjs-multilanguage-isr-ssg-ssr/tree/main/pages) to see that visually in our GitHub repository.

    src/pages
    import React from 'react'
    import Layout from '../../components/Layout'
    import DynamicComponent from '../../components/DynamicComponent'
    
    import Storyblok, { useStoryblok } from "../../utils/storyblok"
    
    export default function Page({ story, preview, locale, locales }) {
      const enableBridge = true; // load the storyblok bridge everywhere
      // use the preview variable to enable the bridge only in preview mode
      // const enableBridge = preview;
      story = useStoryblok(story, enableBridge, locale)
    
      return (
        <Layout locale={locale} locales={locales}>
          <DynamicComponent blok={story.content} />
        </Layout>
      )
    }
    
    export async function getStaticProps({ locale, locales, params, preview = false }) {
      let slug = params.slug ? params.slug.join('/') : 'home'
    
      let sbParams = {
        version: "draft", // or 'publish'
        resolve_relations: ["featured-posts.posts", "selected-posts.posts"],
        language: locale,
      }
    
      if (preview) {
        sbParams.version = "draft"
        sbParams.cv = Date.now()
      }
    
      let { data } = await Storyblok.get(`cdn/stories/${slug}`, sbParams)
    
      return {
        props: {
          story: data ? data.story : false,
          preview,
          locale,
          locales,
        },
        revalidate: 10, // enable static content to be updated dynamically every 10 sec
      }
    }
    
    export async function getStaticPaths({ locales }) {
      let { data } = await Storyblok.get('cdn/links/')
    
      let paths = []
      Object.keys(data.links).forEach(linkKey => {
          if (data.links[linkKey].is_folder) {
            return
          }
    
          // get array for slug because of catch all
          const slug = data.links[linkKey].slug
          let splittedSlug = slug.split("/")
          if(slug === 'home') splittedSlug = false
    
          // create additional languages
          for (const locale of locales) {
            paths.push({ params: { slug: splittedSlug }, locale })
          }
      })
    
      return {
        paths: paths,
        fallback: 'blocking',
      }
    }

    At line 20 and line 47, there are getStaticProps and getStaticPaths Next.js APIs. If you're already familiar with how to set up SSG in your Next.js projects, those Next.js APIs look very similar to SSG setup.

    There are two more lines we'd like to take a look at in these Next.js APIs. In the scope of getStaticProps (line 43), there's an object property,revalidate with a number value.

    revalidate enables static content to be updated dynamically. In this case, it'll keep the stale content within 10 seconds but it'll update content after these 10 seconds expires.

    Static Site Generation (SSG) doesn't have revalidate to return in getStaticProps but Incremental Static Regenerations (ISR) has.

    At line 69 in the scope of getStaticPaths, fallback is returned. In SSG, the value is either true or false. (Mostly false to show 404 page as a fallback.) There's also a value of 'blocking'. Yet, 'blocking' will wait for the HTML to be generated and it'll be cached for future requests to keep caching once per path. It's an ideal value for SSR.

    In Incremental Static Regeneration (ISR), we set the fallback value as 'blocking' to have hybrid advantages from SSR and SSG.

    Now, we are enabled to use static-generation on a per-page basis, without rebuilding the entire site.

    WARN:

    If returning a static page with a loading state on the first request is preferred in your projects, fallback: true could be set up in ISR. However, 'blocking' is still a preferred value.

    Conclusion

    In this blog post, we took a look at the difference between Server Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR) from the logical perspective and in an action.

    If you'd like to watch the details in a video, we have a talk at Next.js Conf 2021. The video will be accessible after the conference.