Right now there are a lot of hot takes out there about how to improve and scale sites “beyond Jamstack” by adding in features that use a Node server. One of those features is called Incremental Static Regeneration.
Some people call it “hybrid web development” (or “hybrid serverless + static”) but let’s talk a bit more about what that means.
What happens when you deploy a Jamstack project
First of all, we should talk about what happens when you build Jamstack sites, and how atomic and immutable deploys work. These are fundamental principles to understand for this style of web development.
Atomic deployment means all of the code, assets, and configuration of a site are updated together at the same time. This means that a website cannot be served in a partially updated state.
Immutable deployment insulates deploys from future actions, guaranteeing their integrity. This means there is always a stable copy of this deploy that can be referenced or re-deployed at any state in the future.
(You can read more about these terms in this blog post)
You can almost think of this as state-driven development. Every single deploy is a new state of your application or site.
If you were to make a mistake on your site, let’s say you broke prod, you deployed the wrong brand colors, or you mixed up some copy, you can instantly rollback to a previous deploy without having to wait for a new build, because that version of your site exists in space. It’s why it works so well with Git, it’s similar to reverting a commit.
I won’t get into the details of the perks of pre-rendering all of your pages, but if you’d like to read more about that, you can check out more information on Jamstack.org.
Incremental Static Regeneration
Incremental Static Regeneration, or ISR, seeks to extend the power of static sites by adding some server-side rendering (SSR) goodies on top.
How it works and why it’s cool
When you deploy a site with ISR enabled, you deploy a (mostly) static site. You have your pre-defined static pages that were built, and you have routes on your application that aren’t built until your users hit those pages.
Typically when you have a server-side rendered (SSR) page that is one of these unbuilt pages, your users have to wait for the page to be built and served all at once. But in the case of ISR, if your users hit that route, they get a fallback page. A fallback page is a placeholder for the actual content that will be on that page, and you can have skeleton components in place until data is built and loaded. Once that data has been resolved, that page is cached, added to the rest of the site’s bundle, and the next user of your page will see the built page. If the data needs to update, the user will see that cached version instead of the fallback, and the site can set a revalidate timeline so that it can revalidate and update data regularly when your users hit the page.
Each of the new blocks in this diagram is a new page that is built at runtime and added to the “stack.”
This method of serving pages is using the stale-while-revalidate caching strategy. It’s pretty dang performant, because you can (nearly) get the performance benefits of a pure static page, with the power of new dynamic data like you would in SSR. That’s why this strategy is very often called “hybrid” development, because it combines the best of both worlds!
Why it’s not great
There’s a few flaws in ISR that you might want to consider before going all-in on the concept.
When you have a user come to your page, you want them to see the most up-to-date version, immediately. With ISR the first visitor to a page will not see that. They will always see a fallback first. And then later, if the data gets stale, the first visitor to see that cached page will see the out-of-date data first before it revalidates. Once again, this inconsistent experience can be pretty difficult to debug if your users experience negative side-effects as a result of old/unbuilt pages.
Remember the whole section up there of atomic and immutable deployment? ISR, unfortunately, breaks that model. By adding extra pages to your bundle, rollbacks can no longer be instant, and you no longer have that single new version of your site when you update your content.
Let’s say you build a site that has a bunch of products for sale, and each of those products are on ISRed pages. In an ideal scenario, your users can navigate to a products’ page, see a product for sale, and buy it. The next users who go to the page will see it, and the page might update to show that the product is out of stock.
If you rollback your site to a different deploy, because your page is cached separately from the bundle, it could exist in a different state for your user than expected. It could be the old version of the site, the new version, or some funky in-between cached version trying to revalidate itself. And unfortunately, debugging this is difficult, because different users (and the dev team!) would see different pages, and it might be difficult to duplicate.
Notice how in this graphic, the pages that are cached separately stick around with their nice big checkmarks, while the rolled-back page is no longer the shipped deploy. If the users navigate to those cached routes, they might see out-of-date data.
The stale-while-revalidate caching that powers ISR is the reaason behind these gotchas. When ISR is based on serving stale content like this, we end up with a pretty big footgun that ultimately is confusing for users, and frustrating for developers.
How does Netlify handle it?
Currently, ISR is built in to Next.js, and we serve those unbuilt pages via Netlify Functions, rendering them new every time, to avoid that caching problem. This isn’t the spirit of ISR, yes, but we are strongly in favor of atomic and immutable deploys. There are better ways to approach your sites than with this type of caching.
We have solutions coming in the future to serve these kinds of unbuilt pages in a better way, keep an eye on this space!
What should I do for my projects?
✨ It Depends ✨
Clearly there are benefits to ISR, but it does come with caveats! Weigh the pros and the cons and decide for yourself if it’s right for you.
Top comments (5)
I like the simplicity of this policy. I've been following the
@netlify/plugin-nextjs
project but I've found it hard to parse exactly what aspects are supported — AFAICT it now supports working with Next v10.x... but does it also support preview mode?Nice!
Excited for a solution which does cache the page instead of regenerating everytime. It does revalidate every second right for each cloud function generated?
netlify / netlify-plugin-nextjs
A build plugin to integrate Next.js seamlessly with Netlify
Essential Next.js Build Plugin
This build plugin is a utility for supporting Next.js on Netlify. To enable server-side rendering and other framework-specific features in your Next.js application on Netlify, you will need to install this plugin for your app.
Table of Contents
Installation and Configuration
For new Next.js sites
As of v3.0.0, all new sites deployed to Netlify with Next.js will automatically install this plugin for a seamless experience deploying Next.js on Netlify!
This means that you don't have to do anything — just build and deploy your site to Netlify as usual and we'll handle the rest.
You're able to remove the plugin at any time by visiting the Plugins tab for your site in the Netlify UI.
UI-based installation for existing Next.js sites
If your Next.js project is already deployed to Netlify…
Thanks! It doesn't need to revalidate because the page is SSRed via the lambda function, so it can show up-to-date data every time.
Nice article Cassidy!
We are currently in the process of moving our e-commerce websites to Jamstack using Netlify.
The main issue we are encountering is build time because we have a product catalog of thousands of pages. Info there is pretty much static and rarely changes because dynamic data like prices is fetched client side. Thus, having ISR would allow us to deploy a new version fast building only the most popular product pages while the rest are being built and updated on demand.
We agree on the drawbacks. However, serving a stale version on our case is very much acceptable because it's very likely that the fresh version is exactly the same and we could take advantage of a very low TTFB.
Thus, could be that without ISR we cannot move to that architecture.
Hope our use case is useful.
Your use case makes sense! It's a very real problem. We're hoping that some of our upcoming solutions can get rid of some of the footguns we've seen (that I talked about here) so that folks like you can have the much shorter build times, without the caching issues.