Partial Prerendering in Next.js 14
Changing the game for static and dynamic pages
April 6, 2024
When building applications with Next.js, we typically think of our pages as being either static or dynamic. In this case, static pages are pages that don't require fresh data during request time, and dynamic pages are the opposite.
In reality though, our dynamic pages are likely made up of both static and dynamic components. With the introduction of Partial Prerendering (PPR) in Next.js's App Router, we can now render pages both statically and dynamically.
In this post, we'll explore why this matters. If you prefer a TLDR, there is one at the bottom!
How do we do things right now?
In the web development world, pre-rendering strategies play a significant role in how our application performs and delivers content to users. In Next.js, we can typically bucket our prerendering strategies into two categories - Static and Dynamic Rendering. Let's delve into these techniques and understand their differences.
Static Rendering: The Prepared Host
Think of Static Rendering as being a well-prepared host. Before your guests arrive, you've already prepared the meals, set the table, and are ready to serve as soon as the doorbell rings. In this scenario, Next.js takes on the role of this host, preparing the pages at build time or in the background upon data revalidation. Once ready, these pages are cached and ready to be served from the Edge, ensuring that guests receive the meals as soon as they request it. This approach works best when you're dealing with data that's consistent across all users and can be prepared beforehand, like a blog post.
You may have guessed though that with this approach, you won't be able to personalise content for the end user, since the page is generated during build time.
Dynamic Rendering: The Personal Chef
On the other hand, Dynamic Rendering is like having a personal chef ready to whip up a meal based on each guest's preferences as they arrive. In the digital realm, this translates to rendering pages at the time of each user request. This method caters to scenarios where data is personalised or can only be determined at the time of the request, such as user-specific dashboard or live scores on a sports page.
However, this approach suffers from the fact that we render the page at runtime on each request. The user won't see anything until the computation has been completed on the server and a response is sent back to the client.
Sounds like we've got our bases covered though?
You might be thinking that this is all sounding pretty good and we can use either method depending on the situation. However, the caveat is that the decision between Static and Dynamic Rendering has to be made on the page level. This means that even if the page contains just 1 dynamic component while the rest of the page is static, the entire page has to be entirely dynamically rendered on each request, which will be slower and more expensive over the long run.
This rigidity can be limiting, since in many real-world applications, a page isn't entirely static or dynamic. Take an e-commerce page for example, which might display a mix of static product listings alongside dynamic, personalised recommendations.
With the release of PPR in Next.js 14, we can now gracefully handle this blend, allowing for pages that are mostly static but with dynamic segments where necessary. Now, part of your page can be prepared ahead of time, while the dynamic components can be rendered on the fly.
What is PPR?
PPR allows us to move away from the rigid mental model of having to serve content dynamically or statically on the page level. With PPR, Next.js combines both static and dynamic pieces of content on the same page, where the static parts are rendered at build time while the dynamic parts can be rendered on each request.
In practice, this means that when a user hits the page, they'll immediately get served a 'static shell', so that they start seeing content straight away. The dynamic parts are then rendered in the background on the server, with static fallbacks served to the user as part of the initial static shell response.
How does it work?
PPR works by taking advantage of React Suspense
. Suspense boundaries let you dictate which parts of your UI should appear together immediately, and which parts should be progressively revealed, with the ability to assign a static fallback whilst they’re being rendered on the server. So in the case of a mostly static page having one dynamic component, its dynamism can be prevented from affecting the rest of the page by wrapping it in a Suspense boundary, ensuring that the rest of the page is pre-rendered as a ‘static shell’ at build time.
At first glance, the user will immediately get a fallback UI as part of the static shell. As soon as the dynamic component is ready though, it’ll be streamed into the UI .
Can I start using this now?
Yes you can! Although as a warning, I probably wouldn't recommend it for production at this stage.
1. Edit your next.config.js
file
Simply add the following experimental flag to your next.config.js
file
1experimental: { 2 ppr: true, 3} 4
2. Wrap dynamic components in Suspense
When creating a page in Next.js, for each dynamic component on the page simply wrap the component in a Suspense
boundary and specify a fallback. Here's some example code from Vercel's PPR demo, which you can check out here.
1import { Suspense } from 'react'; 2import { 3 RecommendedProducts, 4 RecommendedProductsSkeleton, 5} from '#/components/recommended-products'; 6import { Reviews, ReviewsSkeleton } from '#/components/reviews'; 7import { SingleProduct } from '#/components/single-product'; 8import { Ping } from '#/components/ping'; 9 10export default function Page() { 11 return ( 12 <div className="space-y-8 lg:space-y-14"> 13 <SingleProduct /> 14 15 <Ping /> 16 17 <Suspense fallback={<RecommendedProductsSkeleton />}> 18 <RecommendedProducts /> 19 </Suspense> 20 21 <Ping /> 22 23 <Suspense fallback={<ReviewsSkeleton />}> 24 <Reviews /> 25 </Suspense> 26 </div> 27 ); 28} 29
And that's a wrap!
I hope after reading this you have a better understanding of PPR and what problem it's trying to solve. I'm really excited to see this being used in production apps when it's ready!
TLDR
- Dynamic Rendering: With dynamic rendering, we render the page at runtime on each request. The computation all happens on the server and then a response is sent back to the client. The caveat here is that the user doesn’t see anything until the response is returned.
- Static Rendering: With static rendering, there is no computation at runtime because it’s already been done at build time, meaning we can serve the content quickly from a CDN where it’s been cached. However, we can't personalise any of the content on the page.
- Partial Prerendering: PPR takes advantages of both static rendering and dynamic rendering, allowing developers to pre-render parts of the page and serve those immediately, whilst doing computation on the server to serve the rest of the page. Cold starts are a thing of the past!