Partial Prerendering is the Next Big Thing

October 30, 2024

Typically in web development these days we use one of the two rendering strategies:
Static and Dynamic rendering.

Static rendering

Static rendering, or Static Site Generation (SSG), is an HTML route generated at build time. In other words, once you run next build.

The name says it all. This is a static file. Once it's built, it doesn't change and will be returned on every request.

This file can be cached by CDNs and then sent to your users from the edge as fast as possible.
You don't need to make a long request to the origin just to fetch your HTML.

This route can also include external data.
If you're on the Pages router, data can be fetched using getStaticProps as follows:


export default function Table({ rows }) {
  // Cool logic here
}
 
// This gets called at build time
export async function getStaticProps() {
  // Fetch posts from external API
  const res = await fetch('https://.../rows')
  const rows = await res.json()
 
  // The Table components will receive `rows` as a prop at build time
  return {
    props: {
      rows,
    },
  }
}

With the App router and server components, you'd handle the above directly from a component:


export default async function Table() {
  // Fetch data directly in the server component
  const res = await fetch('https://.../rows');
  const rows = await res.json();

  return (
     // Cool logic here
  );
}
 

This is useful when the requests don't contain any personalized data and everything you need is known at build time. For example, a simple blog page with static content would be a perfect candidate.

The drawbacks of this rendering strategy is if you need request data (like user tokens/cookies).
In this case, you'd need to make client side requests all the way back to the origin, resulting in expensive round trips.

Incremental Static Regeneration (ISR)

Anyways… The response you receive at build time during SSG will be the data displayed in the served HTML.

Cool. What if you still want to update these static routes sometimes? Do you need to run a new build just for that?

Well... No.

Incremental Static Regeneration (ISR) is here to save you.

In a nutshell, Next.js provides us with revalidation functions that we can call in order to make a new request, invalidate the cache, and generate a new static route.
The old route will be served to users until the new route is ready, ensuring no downtime.

You can read more about these revalidation functions here: Next.js docs on ISR
Note that the functions are different for the Pages and App router.

Dynamic rendering

So that was static rendering. What about dynamic rendering?

Dynamic rendering means that the HTML route is built at request time.

The benefits of this strategy is that you have access to all request data for any personalized requests you might want to satisfy. You can also access all the data you need straight at the origin, instead of making long round trips.

You might notice that all of the shortcomings mentioned about static rendering are handled by dynamic rendering.

What about the dynamic rendering shortcomings?

You guessed it.
Whatever static rendering is best at, dynamic rendering falls short.

You can't cache the response of dynamically rendered routes in a CDN as the data is not static. This route is re-built on every request, therefore your users need to wait for initial HTML from origin.

Bummer.

Partial prerendering

What if I told you there is a way to combine the best of both worlds? The speed of static rendering and functionality of dynamic rendering?

Let me introduce you to Partial Prerendering.

Note that this is still an experimental feature of Next.js
It needs to be enabled in next.config.ts:


import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}
 
export default nextConfig

Partial prerendering allows you to combine static and dynamic portions into the same route.

This works by prerendering as many portions of your route as possible.
For any dynamic code you have in the route, just wrap a Suspense block around it and the fallbacks will be included in the prerendered HTML.

When a request is made for any of your routes, the prerendered static HTML will be sent directly to your user. This HTML can be cached at the CDN level as explained above.

What comes next is pretty cool.

In the same HTTP request, the dynamic portion of your route is also requested at the origin. The invocation begins for the dynamic portions at the same time as the streaming of the prerendered static portion.

Milliseconds saved.

Thanks to React streaming, the Suspense block fallbacks are replaced with dynamic data as it arrives, and we don't even have to wait for hydration in order for all the pieces to load.

Beautiful.

Conclusion

Although this is still an experimental feature, we should all be excited.
Partial Prerendering is a beautiful way to deliver our AI-infused, buggy projects to users faster than ever.

I love Next.js.