Partial Prerendering

Static Shell + Dynamic Holes

Most pages are mostly static with a few personal parts (cart count, "Hi Priya", recommendations). Before, one personal part made the whole page dynamic. Partial Prerendering (PPR) prerenders the static shell at build time and streams the dynamic parts, which are wrapped in <Suspense>, in the same response.

┌──────────────────────────────────────────────┐
│ Navbar (static)         [Cart: ⏳ dynamic]    │  <- static shell served instantly from CDN
├──────────────────────────────────────────────┤
│ Product title, images, description (static)  │
│ Price (static, revalidated)                   │
│ [Delivery by ⏳ — reads pincode cookie]       │  <- streamed from the server
│ [Recommended for you ⏳]                      │
└──────────────────────────────────────────────┘

Enabling It

In Next.js 16 PPR is part of Cache Components (cacheComponents: true). In Next.js 15 it was experimental.ppr. The code is just Suspense around the dynamic part:

import { Suspense } from 'react';
import { cookies } from 'next/headers';

async function DeliveryEstimate({ productId }: { productId: number }) {
  const pincode = (await cookies()).get('pincode')?.value ?? '560001'; // dynamic API
  const eta = await getEta(productId, pincode);
  return <p>Delivery to {pincode} by {eta}</p>;
}

export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const id = Number((await params).id);
  const product = await getProduct(id); // cached ('use cache' inside)
  return (
    <>
      <h1>{product.name}</h1>
      <Suspense fallback={<p>Checking delivery...</p>}>
        <DeliveryEstimate productId={id} />
      </Suspense>
    </>
  );
}