Partial Pre-Rendering in Next.js 14

Introduction Next.js 14 introduces an exciting experimental feature called Partial Pre-Rendering (PPR). This new approach to rendering aims to improve performance and user experience by blending the best aspects of static rendering and dynamic rendering. In this post, we'll explore what prerendering is, how it works, and why it’s crucial for modern web applications. What is Prerendering? Before diving into Partial Pre-Rendering, let's first understand prerendering and why it matters. Dynamic Rendering: The Traditional Approach Typically, when a server receives a request for a webpage, it generates the page dynamically and sends it to the client. If another user makes the same request, the process repeats. This is known as dynamic rendering. While this approach ensures that the page is always up to date, it has some drawbacks: Performance Issues: Generating a page dynamically for every request increases server load and response time. Scalability Challenges: High-traffic applications may struggle to handle large volumes of dynamic requests. However, dynamic rendering is necessary for certain types of content: Personalized pages (e.g., an e-commerce site showing different recommendations for each user). Frequently updated data (e.g., real-time stock prices or news feeds). Static Rendering: The Optimized Alternative On the other end of the spectrum, many web pages do not need dynamic rendering because their content remains the same for all users. For such pages, we can optimize performance by prerendering them at build time. Instead of generating the page every time a user requests it, the content is rendered once and stored as a static file. These prerendered pages are then pushed to a Content Delivery Network (CDN). When a user requests the page, it is served instantly from the closest edge location, reducing load times and improving scalability. Example of Static Rendering A company’s blog page is a great candidate for prerendering. Since the content does not change frequently, it makes sense to generate it once and serve it as a static file instead of re-rendering it dynamically on each request. The Problem with Single Paradigm Frameworks A Look Back in Time A few years ago, web frameworks typically specialized in a single rendering strategy: Server-Side Rendering (SSR) Static Site Generation (SSG) Client-Side Rendering (CSR) Backend API Endpoints This forced developers to make an all-or-nothing decision about their entire application’s rendering approach, even if different routes required different strategies. As a result, it was common for teams to split their applications across multiple frameworks or even different programming languages. A company might host its blog, dashboard, and API on separate subdomains, each built using different technologies just to support different rendering needs. The Need for Flexibility As user expectations evolved, developers began encountering friction with these single-paradigm frameworks: Dynamic-heavy apps wanted to statically render some pages ahead of time. Static sites wanted to add personalization and interactivity. Server-rendered applications wanted more client-side flexibility. Client-rendered applications wanted better SEO and initial load performance. How Next.js Solves This Problem Next.js gained popularity because it broke free from the single-paradigm approach. It provided teams with: A unified toolset for handling different rendering strategies. A consistent language and routing system across all pages. The ability to mix and match SSR, SSG, CSR, and API routes seamlessly. This made it easier for developers to build modern web applications without managing complex handoffs between different frameworks and backend services. The Problem with Static and Dynamic Rendering Fast forward to today, and once again, we are starting to reach the limits of single rendering paradigms, but this time at a more granular page level. Even the most static pages, like documentation or blog posts, sometimes require dynamic elements. For example: Personalized code snippets tailored to the user. A global navbar displaying the signed-in user's information. At the same time, even highly dynamic pages share large portions of static content across users. The Limitation of Prerendering While prerendering is great for performance, it has an inherent limitation: We cannot prerender a page that depends on runtime information before receiving the request that contains that data. This forces developers into yet another all-or-nothing decision: Prerender the page for better performance but lose the ability to personalize it. Dynamically render the entire page on every request, making it slower and more resource-intensive. This is exactly where Partial Pre-Rendering comes in. Partial Pre-Rendering (PPR) allows us to blend prerendering and dynamic rendering within the same page. St

Mar 16, 2025 - 21:19
 0
Partial Pre-Rendering in Next.js 14

Introduction

Next.js 14 introduces an exciting experimental feature called Partial Pre-Rendering (PPR). This new approach to rendering aims to improve performance and user experience by blending the best aspects of static rendering and dynamic rendering.

In this post, we'll explore what prerendering is, how it works, and why it’s crucial for modern web applications.

What is Prerendering?

Before diving into Partial Pre-Rendering, let's first understand prerendering and why it matters.

Dynamic Rendering: The Traditional Approach

Typically, when a server receives a request for a webpage, it generates the page dynamically and sends it to the client. If another user makes the same request, the process repeats. This is known as dynamic rendering.

While this approach ensures that the page is always up to date, it has some drawbacks:

Performance Issues: Generating a page dynamically for every request increases server load and response time.

Scalability Challenges: High-traffic applications may struggle to handle large volumes of dynamic requests.

However, dynamic rendering is necessary for certain types of content:

Personalized pages (e.g., an e-commerce site showing different recommendations for each user).

Frequently updated data (e.g., real-time stock prices or news feeds).

Static Rendering: The Optimized Alternative

On the other end of the spectrum, many web pages do not need dynamic rendering because their content remains the same for all users.

For such pages, we can optimize performance by prerendering them at build time. Instead of generating the page every time a user requests it, the content is rendered once and stored as a static file.

These prerendered pages are then pushed to a Content Delivery Network (CDN). When a user requests the page, it is served instantly from the closest edge location, reducing load times and improving scalability.

Example of Static Rendering

A company’s blog page is a great candidate for prerendering. Since the content does not change frequently, it makes sense to generate it once and serve it as a static file instead of re-rendering it dynamically on each request.

Image description

The Problem with Single Paradigm Frameworks

A Look Back in Time

A few years ago, web frameworks typically specialized in a single rendering strategy:

Server-Side Rendering (SSR)

Static Site Generation (SSG)

Client-Side Rendering (CSR)

Backend API Endpoints

This forced developers to make an all-or-nothing decision about their entire application’s rendering approach, even if different routes required different strategies.

As a result, it was common for teams to split their applications across multiple frameworks or even different programming languages. A company might host its blog, dashboard, and API on separate subdomains, each built using different technologies just to support different rendering needs.

The Need for Flexibility

As user expectations evolved, developers began encountering friction with these single-paradigm frameworks:

Dynamic-heavy apps wanted to statically render some pages ahead of time.

Static sites wanted to add personalization and interactivity.

Server-rendered applications wanted more client-side flexibility.

Client-rendered applications wanted better SEO and initial load performance.

How Next.js Solves This Problem

Next.js gained popularity because it broke free from the single-paradigm approach. It provided teams with:

A unified toolset for handling different rendering strategies.

A consistent language and routing system across all pages.

The ability to mix and match SSR, SSG, CSR, and API routes seamlessly.

This made it easier for developers to build modern web applications without managing complex handoffs between different frameworks and backend services.

The Problem with Static and Dynamic Rendering

Fast forward to today, and once again, we are starting to reach the limits of single rendering paradigms, but this time at a more granular page level.

Even the most static pages, like documentation or blog posts, sometimes require dynamic elements. For example:

Personalized code snippets tailored to the user.

A global navbar displaying the signed-in user's information.

At the same time, even highly dynamic pages share large portions of static content across users.

The Limitation of Prerendering

While prerendering is great for performance, it has an inherent limitation:

We cannot prerender a page that depends on runtime information before receiving the request that contains that data.

Image description

This forces developers into yet another all-or-nothing decision:

Prerender the page for better performance but lose the ability to personalize it.

Image description

Dynamically render the entire page on every request, making it slower and more resource-intensive.

Image description

This is exactly where Partial Pre-Rendering comes in.

Partial Pre-Rendering (PPR) allows us to blend prerendering and dynamic rendering within the same page.

Static parts of the page can be prerendered ahead of time.

Image description

Dynamic elements are loaded at runtime as needed.

Image description

This approach enables pages to be as static or as dynamic as they need to be, providing the best balance between performance and interactivity.

Current Next.js Behavior

As a reminder, Next.js currently prerenders a page at build time unless it uses dynamic APIs such as:

Incoming request headers

Uncached data requests

When Next.js detects the use of these APIs, it assumes the developer intends to use dynamic rendering and opts the entire page into runtime rendering.

The Problem with This Approach

Right now, if a page uses just one dynamic function, it forces all its parent components (up to the root) into dynamic rendering—even if those components don’t actually use any runtime data.

This means that adding a single dynamic element to an otherwise static page turns the entire page into a dynamically rendered one, eliminating the performance benefits of prerendering.

A Better Way: Isolating Dynamic Elements

What if we could prevent this side effect from spreading upwards?

What if dynamic elements could exist on a prerendered page without opting the entire page into runtime rendering?

What if we could mix and match prerendered and dynamic content more efficiently?

This is exactly what Partial Pre-Rendering (PPR) aims to achieve.

React Boundaries to the Rescue

To solve the challenge of combining static and dynamic rendering, we can use React Boundaries to account for different rendering modes.

Error Boundaries

A page can be in a functioning or non-functioning state. With React Error Boundaries, we can wrap specific parts of a page and define fallback UI in case of errors. If an error occurs inside the boundary, the rest of the page remains unaffected, and the fallback UI is displayed instead.

This prevents a single failure from breaking the entire page and improves resilience in complex applications.

Suspense Boundaries

Similarly, we can wrap components that include asynchronous operations in a React Suspense Boundary. This allows us to design fallback UI for temporary loading states.

When rendering on the server, React immediately streams the fallback UI to the client.

Once async operations complete, the actual content is seamlessly loaded without blocking the rest of the page.

This prevents slow async operations from delaying the initial page load, improving performance and user experience.

Extending Suspense for Partial Pre-Rendering

With Partial Pre-Rendering, Next.js extends Suspense Boundaries even further:

Developers can wrap components that use runtime dynamic APIs in a Suspense Boundary.

This allows the static parts of a page to be prerendered at build time.

Meanwhile, dynamic elements can load later without affecting the prerendered content.

Image description

By isolating dynamic elements inside Suspense, we prevent dynamic APIs from opting the entire page into runtime rendering, preserving the benefits of static optimization.

How Does Partial Pre-Rendering Work?

Deployment Process

When your application is deployed:

The partially prerendered result is pushed to an Edge Network for global distribution.

At runtime, when a user visits the page, edge compute serves the static prerendered result instantly.

Simultaneously, a request is sent to a runtime server to render dynamic parts.

Client-Side Rendering Process

On the client:

The browser starts rendering the prerendered HTML while downloading essential static assets like images, fonts, stylesheets, and JavaScript.

Client-side components become interactive as JavaScript loads.

The server uses runtime data (e.g., request headers, cookies) to fetch and render missing dynamic content.

The browser fills in the dynamic holes by replacing prerendered placeholders with fresh data as they stream in.

Image description

This approach ensures fast initial loading while still supporting personalized and real-time updates.

How Is Partial Pre-Rendering Different?

The Challenge with Dynamic Rendering

When a page is dynamically rendered, there is a delay between the client’s request and the server’s response. This delay can increase due to:

  • Long distances between the user and the rendering server.
  • Slow, uncached data requests.
  • Lack of streaming support.
  • Cold starts in serverless environments.

During this waiting period, users and web crawlers see nothing, and the browser remains idle.

The Advantage of Static Rendering

With static rendering, this delay is shorter since there’s no runtime computation, and the response is served directly from an Edge Network.

How Partial Pre-Rendering Bridges the Gap

Partial Pre-Rendering maintains the benefits of static rendering by:

Serving a fast initial response from the Edge.

Allowing the browser to begin rendering immediately.

Letting the server process dynamic content in parallel and stream updates.

Just as prerendering optimizes pages that don’t change between requests, Partial Pre-Rendering optimizes the parts of pages that remain unchanged, improving both performance and interactivity.

Summary

That covers the concept of Partial Pre-Rendering. While the details can get complex, the good news is:

  • No need to deeply understand how it works to benefit from it.
  • No new APIs to learn—it integrates seamlessly into existing workflows.
  • No upfront infrastructure concerns—Next.js automatically handles optimization.
  • No forced all-or-nothing rendering decisions—static and dynamic content can coexist.

Instead, developers can write their code as if the entire page is dynamic:

  • Use runtime APIs and uncached data requests as needed.
  • Add Suspense Boundaries to progressively stream and break up rendering.
  • Let React and Next.js automatically optimize each part, combining the best of static and dynamic rendering.

Credits: https://www.youtube.com/watch?v=MTcPrTIBkpA