Partial Prerendering (PPR)
Earlier a route was either static or dynamic: one cookies() call made the whole page render on every request. With Partial Prerendering (on by default with Cache Components) a single route is split into two parts:
- Static shell - everything that doesn't depend on the request, prerendered at build time and served from a CDN.
- Dynamic holes - parts inside
<Suspense>that read request data and stream into the same response.
tsx
export default function ProductPage() {
return (
<>
<ProductInfo /> {/* cached - part of the shell */}
<Suspense fallback={<CartSkeleton />}>
<Cart /> {/* reads cookies() - streams in */}
</Suspense>
</>
);
}How streaming works
- The server immediately sends the shell HTML with the
fallbackin place of each hole. - As soon as an async component resolves, React sends its HTML in the same response with a small inline script that swaps it into place.
- Holes resolve independently: a slow block doesn't delay the rest of the page.
Benefits
- Fast TTFB and FCP, as with a static page.
- Personalized data without making the whole route dynamic.
loading.tsxis just a<Suspense>boundary around the page created by convention.
Note:
<Suspense>itself does not make anything dynamic. Only access to request data or uncached I/O turns a component into a hole; synchronous code finishes during prerendering anyway.
updated
did you know it?