The Evolution of Jamstack Rendering Patterns
A look into the direction of the web in 2022, and what solutions we can expect to see come up in the Jamstack ecosystem
In the early days of the Jamstack, developers mostly used it for static sites and opted for a more verbose frontend framework like Vue and React when they needed to perform more sophisticated operations like server-side rendering in web applications. The need for adding dynamic functionalities to web apps never went away, but it didn’t make us appreciate Jamstack any less. We loved what it proposed and the value it provided. Web pages are instantly available to users, and developers can build websites easily and deploy them faster. Users are happy, developers are happy; it’s a win-win.
Then came static site generators which made things better by adding a build process to the previous flow of a static site which meant that all the site’s assets were all pre-generated by a build server (not on a local machine) and then deployed. This was a step forward in improving the developer experience of Jamstack developers and consequently the popularity of this model. Developers could build Jamstack sites with a static site generator like Gatsby, push the project to a version control system like Github, and deploy to a hosting service like Netlify which provides a workflow that will rebuild the site when there’s an update to the project.
Everything seemed great, and we were all better for it.
But like every other technology, Jamstack started evolving as the need for more sophisticated functionalities continued to grow. As a “static site”, a Jamstack site was limited in the things it could do, and people did not keep quiet about it. Suddenly, it seemed like Jamstack was an incomplete model that could not be used at scale. The concerns raised were mostly around the inability to perform server-side operations and the length of build times in larger Jamstack sites. This didn’t sit well within the Jamstack community, and we started to “extend” the Jamstack to solve this new challenge, which it was not originally meant to solve.
Dynamic Functionalities In The Jamstack
While Gatsby made a lot of advancements in how we build and update Jamstack sites with features like incremental builds, Next.js introduced server-side rendering with getServerSideProps():
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
const res = await fetch(`https://.../data`);
const data = await res.json();
// Pass data to the page via props
return { props: { data } };
}
export default Page;
While also maintaining the good-old static generation with getStaticProps()
:
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: { posts},
}
}
export default Blog
This gave developers the idea of a hybrid approach to building Jamstack sites. Suddenly, you could build Jamstack sites that could render different pages with different rendering patterns. For instance, your /about
page could be statically generated while your /cart
page is server-side rendered. However, the issue of long build times remained. But not for long.
With Incremental Static Regeneration (ISR), Next.js also made it possible for pages to be generated on demand and cached for subsequent requests. This meant that developers could have a site with 10,000 pages and only generate 100 pages at build time. All other pages will be dynamically generated on-demand and cached for subsequent requests, effectively bringing the concern about long-running build times to bay.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title}
</li>
))}
</ul>
);
}
export async function getStaticProps() {
const res = await fetch('https://.../posts');
const posts = await res.json();
return {
props: { posts },
revalidate: 10, // In seconds
};
}
export async function getStaticPaths() {
const res = await fetch('https://.../posts', { limit: 100 });
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}));
return { paths, fallback: 'blocking' };
}
export default Blog;
Distributed Persistent Rendering (DPR)
In April 2021, Netlify announced a new rendering pattern called Distributed Persistent Rendering. The idea was to remove the revalidation bit of ISR and make any page that is rendered after the initial build a permanent part of that build. No revalidation. If you want to re-render that page in the future, you will need to trigger a new build. According to the announcement post, this pattern will not compromise the Jamstack principle of immutable atomic deploys.
Along with the DPR announcement, Netlify also launched on-demand builders — A special type of serverless function that generates content on-demand, caches it at the edge, and works across all frameworks. This brought ISR-like capabilities to every other static site generator and meta-framework.
const { builder } = require('@netlify/functions');
async function myfunction(event, context) {
// logic to generate the required content
}
exports.handler = builder(myfunction);
This opened up more opportunities for static site generators like Gatsby to get in on this pattern of delivering web pages with their own adaptation of the concept called Deferred Static Generation (DSG). Eleventy also released the Eleventy Serverless plugin that builds off of the DPR concept to support this rendering pattern for developers.
Deferred Static Generation (DSG)
As mentioned, Gatsby adapted the DPR concept to create a custom DSG rendering pattern that allows developers to defer non-critical pages and only generate necessary content at build time. Like with ISR, the deferred pages are generated on demand and cached. All subsequent requests for those pages are then served from the cache.
// The rest of your page, including imports, page component & page query etc.
export async function config() {
// Optionally use GraphQL here
return ({ params }) => {
return {
defer: true,
};
};
}
The goal of this post is to take a look at the evolution of the Jamstack rendering patterns, where we started, and where we are. At the moment, we’ve come very far from where we started, and for good reason. But personally, I’m thinking, should we have stuck with the initial idea of a Jamstack site? One where we didn’t need to worry about dynamic functionalities. It is 2022, and there’s a lot of nuance around the differences between a Jamstack site and a regular React app.
In Summary
Jamstack has metamorphosed from simply generating and deploying static assets to handling highly dynamic functionalities with advanced rendering patterns and serverless functions.
Yes, there have been advancements in the Jamstack rendering patterns, and it has continued to improve to date. But these improvements consequently added more complexity to an otherwise simple process. In continuing to extend the Jamstack to account for more use cases, we run the risk of overcomplicating it.
But like in every other space, we expect to see continued improvements. 2021 saw the emergence and dominance of meta frameworks like Astro, Slinkity, and Remix — all trying to ship less JavaScript to the browser.
This seems to be the direction the web is taking in 2022, and we expect to see more solutions come up in the ecosystem to significantly improve the Jamstack experience. The emergence of React Server Components in React, Vite as a faster alternative for Webpack and Babel, Edge computing used by the likes of Remix, HTML Streaming, and so on, are all promising solutions that are continuing to gain popularity and adoption. And we can only surmise that things will get better and more interesting, as these technologies penetrate the existing web infrastructure. Safe to say that the best days of the Jamstack are still ahead of us.
The modern web trending practice for building highly optimized sites is shipping less JavaScript. This is where technologies like Remix claim dominance. It is interesting to watch how the Jamstack space continues to evolve, and I’m personally looking forward to what the next step will be.
If you’re building a Jamstack site today, here are the rendering patterns currently available to you:
Static Generation - Pages are rendered once at build time.
Server-Side Rendering - Pages are generated on a per-request basis.
Deferred Rendering (ISR/DPR/DSG) - Critical pages are generated first at build time, and non-critical pages are generated on demand and cached.