The Problem: Static vs Dynamic Tradeoffs
Traditionally, Next.js developers faced a binary choice: static generation (fast but stale) or server-side rendering (fresh but slower). Partial Prerendering (PPR) eliminates this tradeoff by combining both approaches in a single page — a static HTML shell streams instantly, while dynamic content is loaded via React Suspense.
How PPR Works
PPR works by identifying static and dynamic boundaries using React Suspense. During build time, Next.js prerenders the static shell. At request time, it replaces Suspense fallbacks with streaming dynamic content:
// page.tsx — PPR enabled
import { Suspense } from "react";
import { Header } from "@/components/header";
import { PersonalizedGreeting } from "@/components/greeting";
import { ProductList } from "@/components/products";
export default function HomePage() {
return (
<div>
{/* Static — prerendered at build time */}
<Header />
{/* Dynamic — streamed at request time */}
<Suspense fallback={<GreetingSkeleton />}>
<PersonalizedGreeting />
</Suspense>
{/* Static — prerendered at build time */}
<ProductList />
</div>
);
}
Configuration
Enable PPR in your next.config.ts:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
ppr: true,
},
};
export default nextConfig;
For route-level control, export a experimental_ppr config:
// app/page.tsx
export const experimental_ppr = true;
Static Shell and Streaming Boundaries
When a PPR page is requested, the browser immediately receives:
- The static shell — HTML for non-suspended components, CSS, and critical resources
- Suspense fallbacks — placeholder UI shown while dynamic content loads
- Streaming dynamic content — as each Suspense boundary resolves, its HTML is streamed
Timeline:
Request → Static shell (instant) → Fallback UI shown
→ Dynamic content A arrives → UI updated
→ Dynamic content B arrives → UI updated
→ Page fully interactive
Performance Benefits
| Metric | SSR | Static | PPR |
|---|---|---|---|
| First Byte (TTFB) | Slow (server compute) | Fast (CDN edge) | Fast (CDN edge) |
| First Contentful Paint (FCP) | Moderate | Fast | Fast |
| Time to Interactive | Slow | Fast | Fast |
| Dynamic data freshness | Fresh | Stale | Fresh |
| Server load per request | High | Minimal | Low |
Use Cases
PPR is ideal for pages that combine static content with personalized or real-time elements:
E-commerce product pages:
- Static: Product description, images, reviews (cached)
- Dynamic: Inventory status, price (user-specific), recommendations
Dashboard applications:
- Static: Navigation, sidebar, layout shell
- Dynamic: User-specific metrics, notifications, live data
Content sites:
- Static: Article body, metadata, SEO tags
- Dynamic: User comments, related articles (personalized), subscription status
// Product page with PPR
export default function ProductPage({ params }: { params: { id: string } }) {
return (
<>
{/* Static — cached at edge */}
<ProductImages id={params.id} />
<ProductDescription id={params.id} />
{/* Dynamic — streamed per user */}
<Suspense fallback={<PriceSkeleton />}>
<UserSpecificPrice id={params.id} />
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<PersonalizedRecommendations id={params.id} />
</Suspense>
</>
);
}
SEO Implications
PPR pages are fully indexable because the static shell contains all SEO-critical content. Dynamic sections are progressively enhanced, but search engine crawlers receive the complete static content.
- Meta tags: Fully prerendered
- Open Graph images: Static
- Main content: Static, with dynamic enhancements
- Structured data: Included in static shell
PPR vs Other Rendering Strategies
| Strategy | Build Time | Request Time | Edge Caching |
|---|---|---|---|
| Static (SSG) | All pages | Nothing | Full page |
| SSR | Nothing | Entire page | Per-request |
| ISR | Most pages | Revalidation | Full page (stale-while-revalidate) |
| PPR | Static shell | Dynamic boundaries | Shell + streaming |
Conclusion
Partial Prerendering represents a fundamental shift in how we think about rendering strategies. Instead of choosing between static speed and dynamic freshness, PPR gives us both. By wrapping dynamic content in Suspense boundaries, developers can deliver instant static shells while maintaining real-time interactivity for personalized features. As adoption grows, PPR is likely to become the default rendering strategy in Next.js applications.
