On-Demand Static Regeneration in Next.js
The Next.js team introduced incremental static regeneration (or ISR) way back in 2020, allowing statically-generated pages to update their content according to specified time intervals, without requiring a full app rebuild. Mimicking HTTP’s stale-while-revalidate mechanism, a visitor’s page request can kick off an incremental addition to (or replacement of) the static site content, which will then be available immediately to all subsequent visitors to that page, served hot from cache.
In that early generation of incremental static revalidation, the behavior was strictly request-driven: ISR will only kick in when there’s a request for a page not already present in the static cache, and only after a specified revalidation interval. There was no way (or at least no way that wouldn’t be considered a hack) to kick manually off ISR otherwise.
Despite it being a mere point release, Next.js 12.1 kinda turns the situation on its head.
With 12.1, we now have on tap what’s known as on-demand incremental static generation. In short, on-demand ISR gives us the option to trigger static generation outside the context of requests. We can trigger it with a user’s button click, from a page request, from an API request or webhook, from a timer, or by whatever mechanism makes sense for the app. With this, we can effectively eliminate a major source of tradeoffs in traditional static page generation.
An obvious use case for on-demand ISR, as Vercel themselves allude to, is e-commerce. Previously, developers have been forced to consider very carefully as to how to handle product additions, price changes, out-of-stock conditions and so on when leveraging statically-generated product pages, collection pages, and the like. With the prior generation of ISR in Next, users could only see current inventory availability if, for example, we used SSR for product pages, if we’d configured ISR and if a visitor doesn’t make that page request before the next revalidation interval, or if we fetch inventory availability on the client after hydration. Regardless of the exact approach, there are notable tradeoffs.
With on-demand ISR, however, we can, for example, listen on a product inventory update webhook from our e-commerce platform to automatically trigger re-generation of a product page, and provide users with then up-to-date information on product availability.
Note
If you’re running Shopify, check out the official documentation for an overview on working with their webhooks.
An Example Implementation
Let’s look at a simple use case of updating a product’s inventory availability with on-demand ISR. In our example, we’ll set up an API route that listens for webhook requests, retrieving the SKU provided by our hypothetical webhook body, and using that SKU to drive regeneration of our ‘static’ product page:
The magic bullet here is res.unstable_revalidate
. Regardless of the mechanism of how we’re fetching data in our page’s getStaticProps
function, unstable_revalidate
is simply re-triggering the data fetch at the route specified by path
.
In our product page, we implement our component, getStaticPaths
, and getStaticProps
exactly as we usually would. The webhook request handler we specified in webhook.ts
will simply call getStaticProps
at the specified path. If, formerly, we were supplying the revalidate
option to leverage traditional ISR, we can potentially remove it if we can instead fully rely on our webhook to keep all page content up to date. (In practice, you may not want to do this.)
There’s a fair amount of code here to grok, but it’s no different than what we’d otherwise implement for our product pages. This highlights one of the best aspects of on-demand ISR, in that it requires pretty minimal changes to the codebase. The API handler handles the ‘heavy lifting’ for us: whenever the webhook’s fired, the product page is statically regenerated in the background, and every user immediately sees fresh, current product data.
Known Limitations
One of the largest limitations on on-demand ISR, currently, is that a while call to NextApiResponse.unstable_revalidate
will re-run a page’s getStaticProps
function, but will not re-run that page’s getStaticPaths
function. Given this, it’s not currently possible with on-demand ISR for example, to add a new product to our app without triggering a full re-build, or by leveraging either fallback: true
or fallback: 'blocking'
.
Note
Be sure to reference Next.js’s documentation on the
fallback
property carefully for your use case, as well as implementation details specific to fallback pages.
As of the time of writing, another minor limitation is that the revalidation function currently only accepts a single, fully-specified path. To revalidate multiple pages simulatneously from one webhook handler, you’ll need to call NextApiResponse.unstable_revalidate
once for each path.
Wrapping Up
Suffice it to say that, in a pretty healthy number of contexts, it’s possible now with on-demand incremental static regeneration to really close the traditional gaps between server-rendered and statically-rendered apps. With a relatively small amount of extra code, we can selectively re-generate static pages with fresh content without falling back to traditional SSR with getServerSideProps
, without needing to tune our revalidation intervals, and without having to trigger full app re-builds. I’m really excited to see similar features make it into other frameworks and platforms.