Next.js & React

Next.js 15 Partial Prerendering: When It Actually Wins

By Technspire Team
January 8, 2026
10 views

Partial Prerendering (PPR) was the marquee architectural bet in Next.js 15. The promise of a static shell served instantly, with dynamic content streamed in afterwards. A year in, PPR is powerful on the right pages and noise on the wrong ones.

What PPR Actually Does

PPR is a hybrid rendering model. A route is rendered twice at build time: once as a fully static shell (everything that can be), and again with placeholders where dynamic work lives. At request time, the static shell ships instantly from the CDN, and the dynamic regions stream in from the origin inside their Suspense boundaries. The user sees above-the-fold content at the edge's speed; the expensive personalised pieces fill in as they resolve.

When PPR Wins

  • Product pages with personalised slots. Price, stock, and "customers also bought" are user- or geo-specific; the product photo, title, and description are not. PPR ships the static parts at CDN latency and streams the personalised parts.
  • Marketing pages with live data. A landing page with a dynamic "active users" counter or "latest customers" strip is the canonical PPR case.
  • Authenticated dashboards with a shared shell. Navigation, brand, structure. Static. User-specific widgets. Streamed.

When PPR Loses

  • Fully dynamic pages. Admin tools where every byte depends on the logged-in user. PPR adds complexity without a shell to cache.
  • Fully static pages. Content sites where nothing varies by user. Static export or plain ISR is simpler and equally fast.
  • Pages behind strict auth that fail closed. If the user must not see the static shell without an auth check, PPR's shell-first delivery is the wrong model.

The Suspense Boundaries Are the Work

PPR is powered by Suspense, and the quality of your Suspense boundaries determines the quality of your PPR output. A common mistake is wrapping the entire page in a single boundary, which defeats the point. The static shell becomes empty. Wrap individual dynamic regions separately so the build can prerender around them.

// app/product/[slug]/page.tsx
export const experimental_ppr = true;

export default async function ProductPage({ params }) {
  const { slug } = await params;
  const product = await getProduct(slug);        // cacheable, static

  return (
    <main>
      <ProductHeader product={product} />         {/* static shell */}
      <ProductGallery product={product} />        {/* static shell */}

      <Suspense fallback={<PriceSkeleton />}>
        <LivePrice sku={product.sku} />             {/* streamed at request */}
      </Suspense>

      <Suspense fallback={<StockSkeleton />}>
        <StockBadge sku={product.sku} />            {/* streamed at request */}
      </Suspense>
    </main>
  );
}

The Caching Traps

PPR's static shell is built once per deploy, same as any static route. That means the shell can go stale if the content inside it changes at runtime. Two failure modes show up often:

  • CMS content in the static shell goes stale. The shell was built with the old copy; no revalidation trigger fires. Use revalidateTag on publish or pair PPR with ISR on the shell itself.
  • "Dynamic" regions accidentally become static. If every function call inside a Suspense boundary is fully cacheable, the compiler inlines it into the shell. Adding await connection(), headers(), or cookies() marks a region dynamic explicitly.

Edge vs Node for the Dynamic Half

The streamed regions still need a runtime. Edge runtime is fast to start but limited (no native Node APIs, smaller package budget); Node runtime has full capability but colder starts. For most PPR dynamic slots. Small personalised reads, key-value lookups. Edge wins. For anything that hits a traditional database directly, stick with Node.

Migrating from SSR

The migration is mostly mechanical if your App Router code already uses Suspense thoughtfully: enable PPR per-route with experimental_ppr = true, audit your Suspense boundaries, verify the built output shows a non-empty static shell, and measure. PPR is not universal. Measure LCP before and after, and keep the pages that do not improve on their old rendering model.

Summary

Partial Prerendering is an architectural win for pages with a genuine static/dynamic split, and architectural overhead for pages without one. The test is simple: can you name the above-the-fold content that would be identical for every visitor, and the below-the-fold content that must be personalised? If yes, PPR is likely a win. If not, keep the rendering model you have.

Ready to Transform Your Business?

Let's discuss how we can help you implement these solutions and achieve your goals with AI, cloud, and modern development practices.

No commitment required • Expert guidance • Tailored solutions