From Static Files to Hybrid Rendering

The Jamstack started as a model for static sites: pre-built assets served instantly, with a simple workflow that developers could push to a version control system and have a hosting service rebuild and deploy. Static site generators added a build step, which improved the developer experience and boosted the model’s popularity. Tools like Gatsby, paired with GitHub and Netlify, made it easy to generate a full site before deployment.

That initial simplicity was attractive, but it had limits. As projects grew, developers hit two problems: no way to run server-side operations, and build times that ballooned with site size. The Jamstack community responded not by abandoning the model, but by extending it with rendering patterns that mix static and dynamic generation.

Bringing Dynamic Features In

Next.js was a key driver of this shift. It introduced server-side rendering via 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 keeping the original static generation flow available through 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

That hybrid capability meant a site could render an /about page statically and a /cart page per request. Long build times still lingered as a concern, though. Incremental Static Regeneration (ISR) tackled that by letting pages be generated on demand and then cached. A site with 10,000 pages could build only 100 at deploy time and generate the rest lazily; subsequent requests would hit the cache. That effectively resolved the build-time bottleneck.

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

Netlify’s Answer: Distributed Persistent Rendering

In April 2021, Netlify proposed Distributed Persistent Rendering (DPR), a pattern that dropped the revalidation step in ISR. Any page rendered after the initial build becomes a permanent part of that build; to change it, you trigger a new build. Netlify argued this preserved the Jamstack’s principle of immutable, atomic deploys.

Alongside DPR, Netlify shipped on-demand builders, a serverless function type that generates content on request, caches it at the edge, and works across frameworks. That brought ISR-like capabilities to every 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);

Gatsby followed with its own adaptation, Deferred Static Generation (DSG), while Eleventy released a serverless plugin built on the same DPR concept.

Deferring the Non-Essential

DSG lets developers hold back non-critical pages and generate only what’s necessary at build time. Like ISR, deferred pages are generated on demand and cached for later requests.

// 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,
    }
  }
}

Where the Model Stands

The progression from simple static generation to on-demand, cached rendering has been substantial. But the added flexibility came with added complexity. The original Jamstack was easy to reason about; extending it for dynamic use cases has made it harder to define and operate.

That trend continues. The meta-frameworks that emerged in 2021 — Astro, Slinkity, Remix — all aim to ship less JavaScript to the browser. React Server Components, Vite as a faster build tool than Webpack and Babel, edge computing in Remix, and HTML Streaming are gaining ground. These are likely to reshape the Jamstack further, pushing toward faster, leaner sites.

For anyone building a Jamstack site now, the rendering options are:

  • Static Generation — Pages are rendered once at build time.
  • Server-Side Rendering — Pages are generated per request.
  • Deferred Rendering (ISR/DPR/DSG) — Critical pages are generated at build time, with non-critical pages generated on demand and cached.

The tension between simplicity and capability remains unresolved. Still, the direction is clear: the next wave of Jamstack tooling will likely focus on reducing JavaScript and using edge infrastructure to make dynamic rendering faster and cheaper. The best days of the Jamstack may well be ahead.