
Next.js 16 Partial Prerendering (PPR): The Best of Static and Dynamic Rendering

Next.js has long been a leader in giving developers flexible, high-performance rendering strategies - Static Site Generation (SSG), Server-Side Rendering (SSR), Incremental Static Regeneration (ISR), and Client-Side Rendering (CSR) all play roles depending on your use case. With Next.js 14, Partial Prerendering (PPR) was introduced as an experimental feature, starting from Next.js 16, PPR has become a stable and becomes a foundational part of this landscape by letting you blend static pre-rendering and dynamic behavior in the same route, improving perceived performance and SEO without sacrificing real-time data needs.
What Is Partial Prerendering (PPR)?
Partial Prerendering is a rendering strategy that lets Next.js deliver a static HTML “shell” immediately, then stream in dynamic content as it becomes available - all in the same server response. Instead of choosing static or dynamic at the page level, PPR combines both on a per-component basis.
- The static shell (layout, logo, product titles, navigation) is pre-generated at build time or cached ahead of requests.
- Dynamic parts (cart state, recommendations, session data) load later and are streamed to the client using React’s
<Suspense>boundaries. - Users see meaningful UI immediately, with dynamic content filling in seamlessly.
This leads to faster Time to First Byte (TTFB) and a smoother perceived experience, while still allowing data personalization and real-time updates.
How Partial Prerendering Works in Next.js 16
Cache Components: The Heart of PPR
In Next.js 16, PPR isn’t just an experimental flag - it’s integrated into the Cache Components system. Cache Components lets you opt-in to cache certain components or data instead of rendering them on every request, making pre-rendering more predictable and performant.
cacheComponents: trueinnext.config.tsenables PPR and component-level caching.- The new
"use cache"directive lets you mark data-fetching functions or components as cacheable. - Anything not cacheable or depending on request-specific data can be wrapped in a
<Suspense>fallback.
This approach gives you fine-grained control: static UI is reused across requests, while dynamic parts rehydrate or stream in only when needed.
Streaming and Suspense: The Engine Under the Hood
React’s <Suspense>