For years you had to choose: a fast static page, or a personalized dynamic one. Partial Prerendering (PPR) ends that trade-off by serving a static shell immediately and streaming the dynamic parts in as they're ready.
The Mental Model
Next.js prerenders everything it can at build time into a static shell. Anything wrapped in Suspense that reads dynamic data (cookies, headers, uncached fetches) becomes a 'hole' that streams in at request time.
export default function Page() {
return (
<main>
{/* Static — sent instantly from the edge */}
<Hero />
<Features />
{/* Dynamic — streamed in per request */}
<Suspense fallback={<CartSkeleton />}>
<UserCart />
</Suspense>
</main>
);
}Why It Matters
- Time to First Byte stays static-fast because the shell is prerendered.
- Personalized content still appears, streamed without blocking the whole page.
- One page, one request — no client-side waterfall of fetches.
Design for the Shell
Put a good skeleton in every Suspense boundary. The static shell is what users see first, so it should feel complete even before the dynamic holes fill in.
