Partial Prerendering, or PPR, renders the static parts of a page ahead of time and streams the dynamic parts when they are ready. In Next.js 16 it is not a flag you turn on, it is the default behavior when Cache Components is enabled.
The result is a page that feels instant on first visit and still shows fresh, per-visitor data where it matters.
The static shell and the stream
Every route gets a static shell: the prerendered HTML plus a serialized RSC payload for client navigation. The shell holds everything that is known at build time, including cached data and the fallback UI for parts that are not ready.
// app/product/page.tsx
import { Suspense } from 'react'
async function Reviews() {
const res = await fetch('https://api.example.com/reviews')
const reviews = await res.json()
return <p>{reviews.length} reviews</p>
}
export default function ProductPage() {
return (
<main>
<h1>Product</h1>
<Suspense fallback={<p>Loading reviews...</p>}>
<Reviews />
</Suspense>
</main>
)
}The h1 and the loading fallback ship in the shell. The review count depends on a network request, so it streams in once the fetch resolves. The visitor sees the page frame immediately instead of waiting for the slowest part.
How a request is served
The split happens once at build time, then every request reuses it.
The top path is the static shell, produced at build time and served from a CDN without touching your server. The bottom path is the dynamic content, rendered at request time and streamed into the shell once it resolves. A Suspense boundary is the line between the two.
What you can observe
Run next build and the route appears as prerendered rather than rendered on demand. In production, the x-nextjs-cache response header reports cache state with values such as HIT, STALE, MISS, and REVALIDATED.
Cached output with a short lifetime behaves differently. A cacheLife with a revalidate of zero, or an expire under five minutes, is excluded from the shell and resolves at request time as a dynamic hole. That is how you deliberately keep one small part fresh while the rest stays static.
Common confusion
Suspense provides fallback UI, but it does not make a component dynamic. A synchronous component finishes during prerendering whether or not it is wrapped in Suspense.
The inverse also surprises people: reading a cookie no longer pushes the entire route into dynamic rendering the way the old model did. Only the boundary around the read streams, while the rest of the page stays in the shell.
One more distinction is worth keeping straight. When a route's dynamic params are known at build time, the shell holds that concrete content. When they are not, Next.js prerenders the reusable, URL-independent version instead, called the App Shell, and ISR upgrades it in the background after the first visit.
Rune AI
Key Insights
- PPR renders a static shell at build time and streams the rest at request time.
- It is the default in Next.js 16 under Cache Components, with no flag to set.
- Suspense boundaries mark where the static shell ends and streaming begins.
- Cached output joins the shell while uncached and runtime data stream.
- The shell is HTML plus an RSC payload, so it can be served from a CDN.
Frequently Asked Questions
Do I need to enable PPR in Next.js 16?
Is PPR the same as streaming?
Conclusion
Partial Prerendering splits one route into a static shell and streamed dynamic parts. The shell ships instantly from a CDN, and Suspense boundaries mark where fresh content streams in.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.