On-demand Static Regeneration: How Blogody serves multi-zone blog sites
Never heard of On-demand Static Regeneration? No wonder! It's a new acronym that we invented for this article in order to describe an exciting new evolution of how we can generate and publish blazing fast static web sites on the web.
There is currently a lot of heated debate about new ways of building Jamstack sites, with new approaches that are called beyond Jamstack, hybrid web development or hybrid serverless + static.
In this article, I am introducing a new addition to the existing hybrid approaches, that I call On-demand Static Regeneration (OSR).
On-demand Static Regeneration
OSR is based on Incremental Static Regeneration (ISR) that was invented by NextJS. Consider a web page that has not been statically built. With ISR, the first visitor sees a fallback page, for example a skeleton of the real site. The cool thing about ISR is that this first request triggers a static regeneration, which means all subsequent requests receive a fully built static site.
On-demand Static Regeneration is an extension to that process, in that the first request is not made by a visitor, it is automatically triggered by a web hook. We'll see later in this article, that there are always natural events that can be used as a trigger.
In effect, every visitor to your site always receives a static built, blazing fast, up-to-date site.
Before going into more details, let's take a step back and put everything into context.
Jamstack & Static Site Generation
The modern way to build websites that deliver blazing fast performance is based on the so called Jamstack architecture. It's main conceptional principles can be summarized as follows:
Decouple the front-end from the back-end.
Build as much as possible up-front through static site generation (SSG).
Publish to the content-delivery-network (CDN), so it serves your content as pre-built assets.
This approach is fundamentally different from a traditional web server, where web sites are rendered on every page request (SSR). A page that is SSR rendered cannot be put on a fast CDN edge.
That's why static site generators (SSG) have become so popular: pre-built content can be distributed globally through a CDN and serve pages from edge gateways closest to where you are requesting the site. This allows for a much faster user experience and has a positive effect on Google page rankings.
Atomic & Immutable Deployments
While the previously outlined Jamstack principles do not say anything about the deployment process, most CDN providers use the method of atomic deployments similar to Git commits. This means the entire site (code, assets, and configuration) is updated together at the same time.
Atomic deployments are immutable with sealed content and guaranteed integrity. Every atomic deployment gets an identifier that's never going to change. Any update will therefore produce a new deployment, similar to state-driven development where any change produces a new state in your application.
Why would you want atomic and immutable deployments? It allows you instantly rollback your site to a previous state without the need to re-build your site. For example, when you discover a mistake on your site after deployment, be it a typo or mixed-up brand color, you can instantly revert back to your any of your previous deploys. It's as easy as a git reset.
Static Sites with Dynamic Content
While pure static sites have their use cases, most web sites need some app-like features, that is, dynamic changes during runtime. A simple example for such a dynamic feature would be the number of likes of an article. Of course, you can statically re-build your site on a pre-defined schedule, but it's much nicer if the user giving a like, instantly sees it updated on the website.
While your deployment stays immutable as such, the content presented to the user (including the number of likes) is not defined by the unique deployment identifier anymore. It's total state now also depends upon the current state of your database.
In this contrived example, this is probably exactly what you want: Even when reverting to a previous deployment, you certainly wish the site to show the latest number of likes. However, the more app-like content you introduce into your site, the more it looses the property pf immutability.
If your web site uses app-like features with client-side dynamic content, atomic and immutable deployments do not guarantee an immutable web site.
The take-away from these examples is that the total state of an app-like static web site is governed by both your deployment state and the dynamic back-end states resulting in a combined state that cannot be controlled by a unique identifier. As such, supercharged Jamstack sites break that model of immutability to the degree of integrated app-like features.
Static Site Generation Drawbacks
Another drawback of pure static site builders is the time it takes to build an entire web site. While this is certainly not a problem for a handful of pages, it becomes near impossible to re-build a web site with thousands of pages, especially if paired with image optimizations.
GatsbyJS' answer to this problem is called Incremental Builds, the idea being that you only re-build sites that changed since last deployment. While the idea sounds simple enough, it's a non-trivial task to assess possible dependencies between your pages and the GatsbyJS incremental build feature turned out very flaky (I was among the first users, so I hope it has improved by now). In principle, atomic deployments can be retained with this technique.
Apart from the stability issues that may be overcome in the future, incremental builds have another, more severe drawback: It doesn't give you any benefit when you build your site for the first time. Depending on your site, it can take many hours to complete. Just imagine, you'll find a bug in between that initial build process. Only subsequent builds can leverage the previous build process.
With all the recent advances of pure static site builders one challenge still remains: Building large web sites with many pages and heavy assets (images, videos, etc.) for the first time is an extremely time consuming process.
Incremental Static Regeneration
While incremental builds need a first static build, NextJS came up with another clever idea. Incremental Static Regeneration (ISR) extends the power of static sites with traditional server-side rendering (SSR).
Instead of starting with a full static build, you start with a handful, critical static pages. This gives you more control over the initial build times.
Whenever a user requests a pre-build static page, it'll receive a super fast static web site. But what happens, if the page has not been pre-build? In this case NextJS will use SSR to render the page for you, but in the background it will fire a static-site generation of the same page, so all subsequent visits to the same page will result in serving a static site again. Note that it is only the very first user that triggers the regeneration and that all subsequent visitors will see the static page.
This approach is called hybrid, because you (nearly) get the performance benefits of pure static site builders combined with the power of fully dynamic SSR sites.
If you read the argument carefully, you'll find out that it does not go against the core idea of ISR, it simply points towards the difficulty of purging the cache when you loose the property of immutable deployments.
How does On-demand Static Regeneration fit into this landscape? It solves the following two challenges:
All visitors always get a super-fast statically built site.
There are no up-front static built times, deploys complete within seconds.
Too good to be true, what's the catch? First and foremost your deploys loose the property of immutability. Formally, immutability still exists for a single deployment, but as your deployment only consists of skeleton pages, its benefits have become only of minor importance.
Thus, the state of your web site is governed mainly by the state of your database, which holds the content from which the static pages are generated on-demand.
Let's take a look into how OSR achieves near zero deploy times. The skeleton pages do not contain much content, that's why a deploy can be so fast. The burden on generating static sites has been split into many small pieces (pages) and spread over a larger time-span. In contrast to ISR, regeneration does not occur on first visitor request, for OSR it occurs on creation time.
Creation time is the first event when your content is ready to be shown to the world. For a blog article, it is the time when you hit the publish button and that's when OSR sends an automatic first request to your new or updated page, effectively regenerating your static site.
If you sum up all the regeneration times of all the web pages of a web site it should equal the build times of a static site generator that builds all sites in a single build step. However, due to slicing your page into smaller units (i.e. pages) and spread the build process over time (i.e. creation times), you easily overcome the static build walls present in traditional static site builders.
Multi-Zone On-demand Static Regeneration
While OSR has come immediately to my mind when I first learned about ISR, there was still one technical challenge that I was not able to overcome easily.
For Blogody, the new SaaS blogging platform I am currently creating, I need a multi-zone set-up, also known as a multi-host or multi-tenant architecture. Users of this platform will receive a dedicated sub-domain for every blog they are creating.
For example, one user may have a blog on https://eager-einstein.blogody.com, another user on https://vibrant-williams.blogody.com. Thus, blog pages are organized on different subdomains. As user's can create new blogs all the time, those subdomains are not known up-front.
The problem is that NextJS static site generation functions do not capture the subdomains, or host information. While you can use OSR techniques to regenerate sites, I couldn't find a way to fetch data based on the varying subdomains. Of course, you could make a new deployment for every new subdomain created in the back-end, but this would let the number of deployments explode: not a scalable solution.
Once your static build functions can grab the subdomain info, combining it with OSR is easy.
By integrating the latest hybrid approaches of building modern web sites into an event driven content workflow, and thanks to the more flexible rewrite capabilities of NextJS, it is now possible to deliver on-demand static regeneration on multi-zone sites.
The benefits are striking: Always up-to-date, flaring fast static sites that can be served globally from the CDN edge.