Partial Prerendering Revealed: The Best of Static & Dynamic in One Route

Ralph Sanchez

Partial Prerendering Revealed: The Best of Static & Dynamic in One Route

For years, web developers have faced a tough choice: build a fast, static site that's hard to personalize, or a slow, dynamic site that's built from scratch on every request. Next.js 14 introduced an experimental feature that promises to end this dilemma: Partial Prerendering (PPR). This article reveals how PPR works, allowing you to serve a blazing-fast static shell while streaming in dynamic content, giving you the best of both worlds in a single route.
This innovative approach complements other modern Next.js features, like the API-free pattern of the Server Actions goldmine, which simplifies data mutations. To take performance even further, you can learn how to deploy functions to the Edge, bringing your logic closer to your users. When you're ready to implement these advanced strategies, you can hire Next.js developers to build next-generation web experiences.

The Old Dilemma: Static vs. Dynamic Rendering

Before diving into how PPR changes the game, let's understand the problem it solves. Web developers have traditionally chosen between two rendering approaches, each with significant trade-offs.

The Speed and Simplicity of Static (SSG)

Static Site Generation has been the go-to choice for performance-focused developers. When you build a static site, Next.js generates all your HTML pages at build time. These pages get served directly from a CDN, which means users get near-instant page loads regardless of their location.
The benefits are hard to ignore. Your pages load in milliseconds. There's no server processing on each request, so hosting costs stay low. Security risks drop significantly since there's no dynamic code execution. Plus, your site can handle massive traffic spikes without breaking a sweat.
But here's the catch: every user sees the exact same content. Want to show a personalized greeting? Display user-specific recommendations? Show real-time inventory levels? You're out of luck. The content is frozen at build time, making personalization nearly impossible without client-side JavaScript gymnastics.

The Power and Pain of Dynamic (SSR)

Server-Side Rendering takes the opposite approach. Every time a user requests a page, your server builds it from scratch. This opens up a world of possibilities. You can greet users by name, show their shopping cart contents, display location-specific pricing, or pull in real-time data from your database.
The flexibility is incredible, but it comes at a cost. Each request needs server processing time, which means slower initial page loads. Your Time to First Byte (TTFB) suffers as users wait for the server to build their page. Server costs climb as traffic increases. And during traffic spikes? Your server might buckle under the load.
It's a classic trade-off: speed versus flexibility. Until now, you had to pick a side.

The 'All or Nothing' Problem in Next.js

Here's where things got frustrating for Next.js developers. Imagine you have a product page that's 95% static content: product descriptions, images, reviews. But you also want to show the current user's shopping cart count in the header.
In traditional Next.js, that single dynamic element forces the entire page into dynamic rendering. All that static content that could be served instantly from a CDN? It now gets rebuilt on every request. You lose all the benefits of static generation because of one small dynamic component.
This all-or-nothing approach felt like using a sledgehammer to crack a nut. Developers needed a more nuanced solution.

What is Partial Prerendering (PPR)?

Partial Prerendering is Next.js's answer to this rendering dilemma. Instead of forcing you to choose between static and dynamic for an entire page, PPR lets you mix both approaches within a single route.
Think of it like building a house. The foundation, walls, and roof (your static content) get built ahead of time. But you leave spaces for windows and doors (your dynamic content) that can be installed later based on the homeowner's preferences.

The Static Shell: A Fast First Impression

With PPR, most of your page becomes a static shell that's prerendered at build time. This includes your layout, navigation, footer, and any content that doesn't change between users. This shell gets served instantly from the Edge, just like a traditional static site.
Users see meaningful content immediately. The page feels fast and responsive. Search engines can crawl your content without waiting for dynamic data. Your Core Web Vitals scores improve dramatically.
The beauty is that this isn't a loading state or a skeleton screen. It's real, valuable content that users can start reading and interacting with right away.

Dynamic Holes: Streaming Personalized Content

While users are already engaging with your static content, Next.js streams in the dynamic parts. These "holes" in your static shell get filled with personalized data specific to each user.
Maybe it's their username in the header. Their cart count. Personalized product recommendations. Real-time stock levels. This content streams in progressively, updating the page without jarring layout shifts or loading spinners.
The key insight? Users don't need to wait for everything to load before they can start using your site. They get the best parts of static sites (instant loads) and dynamic sites (personalization) working together seamlessly.

How PPR Works: The Magic of React Suspense

The technical implementation of PPR might surprise you with its simplicity. There are no new APIs to learn or complex configurations to master. It's all built on React features you might already be using.

Suspense as the Boundary

React Suspense is the secret sauce that makes PPR possible. When you wrap a component in <Suspense>, you're creating a boundary between static and dynamic content.
Here's what happens: Next.js prerenders everything outside the Suspense boundaries at build time. When it encounters a Suspense boundary, it doesn't wait for the async component inside. Instead, it renders the fallback UI and moves on.
<Suspense fallback={<CartSkeleton />}>
<ShoppingCart /> {/* This loads dynamically */}
</Suspense>

This simple wrapper tells Next.js: "Hey, this part might take a while to load. Don't let it block the rest of the page."

The Role of the fallback UI

The fallback UI isn't just a loading state that disappears. It becomes part of your static HTML, embedded directly in the initial response. This is crucial for preventing layout shift.
When you define a skeleton or placeholder in your fallback, make sure it matches the size and shape of your dynamic content. This way, when the real content streams in, it slides perfectly into place without pushing other elements around.
Smart developers use this to their advantage. Your fallback can show meaningful placeholders that give users context about what's loading. A cart skeleton shows where the cart will appear. A recommendations skeleton indicates personalized content is coming.

A Single, Optimized HTTP Request

Here's where PPR gets clever. Despite serving content in two stages (static shell, then dynamic streams), everything happens in a single HTTP request. There are no additional round trips to the server.
Next.js uses HTTP streaming to send the static shell immediately, then keeps the connection open to stream dynamic content as it becomes ready. This approach minimizes latency and maximizes performance.
From the browser's perspective, it's receiving one continuous response. But under the hood, Next.js is orchestrating a sophisticated dance of static and dynamic content delivery.

Implementing Partial Prerendering in Your App

Ready to try PPR in your own projects? The good news is that implementation is straightforward, especially if you're already using React Suspense.

Enabling the Experimental Flag

First, you'll need to enable PPR in your Next.js configuration. Add this to your next.config.js:
module.exports = {
experimental: {
ppr: true
}
}

Remember, PPR is still experimental. While it's stable enough for testing and some production use cases, keep an eye on the Next.js release notes for updates.

Identifying Dynamic Content

Before implementing PPR, take a step back and audit your pages. Which parts truly need to be dynamic? Often, less content needs personalization than you might think.
Good candidates for dynamic content include:
User-specific data (profile info, preferences)
Shopping cart contents
Real-time inventory or pricing
Location-based content
Personalized recommendations
Live chat widgets
Everything else? That's your static shell. Navigation menus, product descriptions, blog content, marketing copy – these rarely need per-request rendering.

Code Example: An E-commerce Page

Let's see PPR in action with a real-world example. Here's a product page that combines static product info with dynamic user-specific elements:
// app/products/[id]/page.jsx
import { Suspense } from 'react'
import ProductDetails from '@/components/ProductDetails'
import AddToCartButton from '@/components/AddToCartButton'
import UserRecommendations from '@/components/UserRecommendations'
import CartCount from '@/components/CartCount'

export default async function ProductPage({ params }) {
// Static product data fetched at build time
const product = await getProduct(params.id)

return (
<div>
{/* Static header with dynamic cart count */}
<header>
<nav>
<Logo />
<Suspense fallback={<div>Cart (0)</div>}>
<CartCount />
</Suspense>
</nav>
</header>

{/* Static product information */}
<main>
<ProductDetails product={product} />

{/* Dynamic add to cart with inventory check */}
<Suspense fallback={<ButtonSkeleton />}>
<AddToCartButton productId={product.id} />
</Suspense>

{/* Dynamic personalized recommendations */}
<Suspense fallback={<RecommendationsSkeleton />}>
<UserRecommendations currentProduct={product.id} />
</Suspense>
</main>
</div>
)
}

The static parts (product details, layout) load instantly. The dynamic parts (cart count, inventory status, recommendations) stream in without blocking the initial render.

The Benefits of Adopting Partial Prerendering

PPR isn't just a clever technical solution. It fundamentally improves how we build and experience web applications.

Unbeatable Performance

PPR delivers the holy grail of web performance: instant initial loads with dynamic personalization. Your static shell loads as fast as a CDN can deliver it. Dynamic content streams in without blocking user interaction.
This translates to real-world improvements. Better Core Web Vitals scores. Lower bounce rates. Higher user engagement. When pages feel instant, users stick around.
The performance gains are especially noticeable on slower connections or devices. While traditional SSR might leave users staring at a blank screen, PPR shows useful content immediately.

Improved SEO

Search engines love fast-loading pages with immediately available content. With PPR, your static shell is ready for crawlers the moment they arrive. No waiting for dynamic data. No JavaScript execution needed.
But here's the clever part: you can still include dynamic content for logged-in users without hurting SEO. Search engines see the static shell with all your important content. Real users get the full personalized experience.
This dual approach means you don't have to compromise between SEO and user experience anymore.

Simplified Development Model

Perhaps the biggest win is the mental model shift. Stop thinking about pages as either static or dynamic. Start thinking about components.
Is this component the same for all users? Make it static. Does it need user-specific data? Wrap it in Suspense. The decision happens at the component level, not the page level.
This approach aligns perfectly with how we actually build applications. You're no longer forced to make architectural decisions based on rendering limitations. Build the experience users need, and let PPR handle the optimization.

Conclusion

Partial Prerendering represents a fundamental shift in how we think about web performance. By combining the best aspects of static and dynamic rendering, it eliminates the traditional trade-offs that have plagued web developers for years.
The implementation is surprisingly simple, building on React patterns you already know. The benefits are immediate and measurable. And the developer experience finally matches how we naturally think about building applications.
As you explore PPR, remember it's part of a larger ecosystem of Next.js innovations. Combine it with Server Actions for seamless data mutations. Deploy to the Edge for global performance. The future of web development isn't about choosing between static and dynamic – it's about using both intelligently.
Start small. Pick a page with mostly static content and a few dynamic elements. Implement PPR and measure the results. Once you experience the performance gains firsthand, you'll wonder how you ever built sites any other way.

References

Like this project

Posted Jun 19, 2025

Stop choosing between static speed and dynamic content. Discover Next.js Partial Prerendering (PPR) to serve a fast static shell with dynamic content streamed in.

Edge-All-Things: Deploying Functions Worldwide Without the DevOps
Edge-All-Things: Deploying Functions Worldwide Without the DevOps
Turbopack Takeover: Slashing Build Times from Minutes to Milliseconds
Turbopack Takeover: Slashing Build Times from Minutes to Milliseconds
Next.js 15 Speed Hacks: 7 Tweaks for a Perfect Lighthouse Score
Next.js 15 Speed Hacks: 7 Tweaks for a Perfect Lighthouse Score
Server Actions Goldmine: The API-Free Pattern for Modern Next.js Apps
Server Actions Goldmine: The API-Free Pattern for Modern Next.js Apps

Join 50k+ companies and 1M+ independents

Contra Logo

© 2025 Contra.Work Inc