Using Next.js ISR with Serverless Cloud

Apr 28, 2022

In this article I'll introduce you to an amazing feature of Next.js called Incremental Static Regeneration (ISR) and show you how to enable it in your Serverless Cloud Next.js applications.

You're probably already familiar with static generation, which creates static pages at build time. ISR is similar to static generation but it lets you build static pages at runtime instead of at build time. Why would you want to do that? Two reasons: to cut down your build time, and to allow your static pages to be updated without having to do a full rebuild of your site.

This is especially useful if you have a big site. Imagine you have a product site with hundreds or even thousands of product pages, or a blog site with tons of posts. It could take several minutes or even hours to build the whole site as static pages, and the build time will get even worse as you add more pages.

One way to deal with this scenario would be to use server-side rendering (SSR) to generate the product pages on demand and enable caching so browsers and the CDN can cache the results. However there are some drawbacks to this approach. Pages will need to be generated multiple times, once for each CDN access point, which means more invocations of your app and more data queries. It will also result in a slower response time for those initial requests that aren't cached in the CDN.

ISR gets around these issues by statically generating your pages in the background on-demand, instead of at build time. This allows your build to be fast, and your pages can still be statically generated and cached across all regions.

The way ISR works in Next.js is pretty amazing. When a request is made for a page that hasn't been statically generated yet, it will render a fallback page and then dynamically load the data and hydrate the page client-side. Meanwhile the page gets statically generated in the background, so subsequent requests for the page will return the static content without invoking your app or hitting your database.

You can also control how often a static page is regenerated by giving it an expiration time using the "revalidate" option. When a request is made for a stale page, the initial request will return the cached data and it will be updated again in the background. I call this "lazy revalidation" since we only revalidate pages if they are stale, and only when they are requested.

Blog Example

Let's take a look at an example blog site. I built this example by starting with the original Next.js typescript blog starter and then I converted it to use Serverless Data instead of local markdown files so I can edit blog posts and create new blog posts without redeploying.

You can get the example by cloning the serverless/cloud repo at https://github.com/serverless/cloud. Open a terminal and go to the examples/nextjs-blog directory, install dependencies, import the sample data, and start the local dev server:

In your terminal:

cd examples/nextjs-blog
npm i
npm i -g @serverless/cloud
cloud dev
> import

This will open the Cloud shell and start the Next.js dev server. Then in the shell type "import" to import data.json into Serverless Data:

> import
✓ Data Imported from "data.json"

That will start the dev server at http://localhost:3000. If you open that in your browser you should see the sample blog posts:

One important point about ISR is that it only works in "stage" instances, so you need to deploy to see the magic. You also need at least one page to be statically generated, so we need to load the sample and data and then deploy again:

> deploy production
> import –instance production
> deploy production

After the second deployment you can visit the production URL and you'll see the same blog posts as in dev.

If you look at `pages/posts/[slug].tsx` you'll notice that the project is set up to statically generate just the first two blog posts, which in this case are the "hello-world" and "preview" pages because those are the two newest according to the sample data.

export async function getStaticPaths() {
 const slugs = await getPostSlugs();
 return {
   paths: slugs.slice(0, 2).map((slug) => ({
     params: { slug },
   })),
   fallback: true,
 };
}

If you open up your browser's dev tools and visit the "hello-world" page, you'll see that the page has been statically generated, so the page response includes the generated HTML content:

The "fallback: true" setting in `getStaticPaths()` tells Next.js to return a fallback page for pages that haven't been statically generated. In our case, the "dynamic-routing" page hasn't already been generated, so if we visit it with dev tools open we see the empty "fallback" page:

After loading the fallback page, the browser retrieves "dynamic-routing.json" to get the page data which gets inserted into the page. Pretty cool right? But wait, it gets better.

This is where the magic happens behind the scenes. Requesting the non-generated page triggered a revalidation in the background. If you retrieve the page again you'll get static content and no need to request the page data:

The page is now static HTML stored in your Serverless Storage. Any more requests for this page won't invoke your app and won't make any database requests. The static content will also be cached globally in the CDN. Is that cool or what?

That covers what happens when a request is made for a page that wasn't generated at build time, but ISR also takes care of regenerating pages that were generated at build time.

You'll notice that I specified "revalidate: 10" in `getStaticProps()`. This tells Next.js that the page is stale after 10 seconds. In the real world I'd definitely suggest a longer expiry time, this is just a demo! Let's change the title of the "hello-world" post to "Hello ISR!", by editing the data item in the Cloud dashboard:

If you visit the hello-world page again, you'll notice that the title doesn't change immediately. This is how ISR works, it returns the previously cached page, and then updates the page in the background. So if you visit the page again, you'll see the new title:

On-demand Revalidation

So far we've been lazily regenerating static pages in the background only when the pages are requested. The initial request for the page returns the current static version of the page, which could be stale, and the new content isn't available until later after the page is regenerated.

We can take this one step further using "On-demand Revalidation". On-demand revalidation is a Beta feature of Next.js that you can use to explicitly trigger an update to a page.

Serverless Cloud supports on-demand regeneration using the Next.js built-in `res.unstable_revalidate()` method described in the docs.

Server Cloud also lets you trigger a revalidation by publishing a `nextjs.revalidate` event. You can enable on-demand revalication in the blog example by uncommenting the code in `index.ts`:

// Trigger on-demand revalidation when a blog post changes
data.on("*:post:*", async ({ item }) => {
 console.log("revalidating", item.value.slug);
 
 // update the post page
 await events.publish("nextjs.revalidate", {
   path: `/posts/${item.value.slug}`,
 });
 
 // update the home page in case the post appears in the "More Stories" list
 await events.publish("nextjs.revalidate", { path: "/" });
});

Now, any time a blog post is updated in the database, the page is regenerated immediately! Go ahead and modify a blog post then reload the page in your browser to see the latest content.

Conclusion

Next.js ISR is an extremely powerful feature that is now fully supported by Serverless Cloud. You can now build Next.js sites with statically generated pages that can be updated without needing to rebuild your entire site. Give it a try and let us know what you think!

If you haven't already, you should check out the Serverless Cloud docs and join us on Discord or Slack so you can reach out if you have any questions or feedback, or just to say “Hi!”

Try Serverless Console

Monitor, observe, and trace your serverless architectures.
Real-time dev mode provides streaming logs from your AWS Lambda Functions.

Subscribe to our newsletter to get the latest product updates, tips, and best practices!

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.