---
title: "Master Next.js 16 Partial Prerendering in 5 Easy Steps"
slug: "nextjs-16-partial-prerendering-guide"
published: "2025-11-02"
updated: "2025-10-29"
validated: "2025-10-29"
categories:
  - "Next.js"
tags:
  - "Next.js 16"
  - "Partial Prerendering"
  - "PPR tutorial"
  - "e-commerce optimization"
  - "static vs dynamic rendering"
  - "Next.js performance"
  - "React server components"
  - "web development guide"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "next.js"
  - "typescript"
status: "stable"
llm-purpose: "Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today."
llm-prereqs:
  - "Access to Next.js"
  - "Access to TypeScript"
llm-outputs:
  - "Completed outcome: Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today."
---

**Summary Triples**
- (Next.js configuration, enables Partial Prerendering, experimental.ppr = 'incremental' in next.config.ts)
- ('incremental' PPR mode, requires, explicit per-route or per-component opt-in (not global))
- (Partial Prerendering, lets you, keep a route prerendered while deferring only sections that use request-time APIs (e.g., cookies(), headers()))
- (PPR best practice, isolate, request-time data access into small server component boundaries to avoid making the whole route dynamic)
- (PPR tradeoff, contrasts, ppr: true (global) vs ppr: 'incremental' (opt-in safer for selective use))
- (Validation, requires, production build (next build + next start) and checking which sections are prerendered vs deferred)

### {GOAL}
Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today.

### {PREREQS}
- Access to Next.js
- Access to TypeScript

### {STEPS}
1. Enable PPR in Your Configuration
2. Export the PPR Experimental Flag
3. Build Static Components Normall
4. Create Dynamic Components
5. Combine with Suspense

<!-- llm:goal="Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today." -->
<!-- llm:prereq="Access to Next.js" -->
<!-- llm:prereq="Access to TypeScript" -->
<!-- llm:output="Completed outcome: Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today." -->

# Master Next.js 16 Partial Prerendering in 5 Easy Steps
> Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today.
Matija Žiberna · 2025-11-02

I was building an e-commerce dashboard last month when I hit a frustrating wall. The product listing page loaded instantly for everyone beautiful, cached, perfect. But the moment I needed to show a personalized banner at the top (pulling the user's session from cookies), the entire page suddenly became dynamic. Every visitor now had to wait for server processing instead of getting the pre-rendered version. The static benefits disappeared across the whole route, not just the small section that actually needed the cookie data.

That's where Next.js 16's Partial Prerendering (PPR) comes in. Instead of choosing between "fully static" or "fully dynamic," you can now keep the page statically prerendered by default and explicitly defer only the sections that need request-time data like `cookies()` or `headers()`. The rest of the page stays lightning-fast.

In this guide, I'll walk you through exactly how I implemented this pattern and how you can use it in your own projects.

---

## Step 1: Enable PPR in Your Configuration

First, you need to tell Next.js that you want to use Partial Prerendering. This happens in your `next.config.ts` file:

```ts
// next.config.ts
import type { NextConfig } from 'next'

const config: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}

export default config
```

The `incremental` setting is important here. It means PPR won't automatically apply to every route in your app. Instead, you explicitly opt-in on a per-route basis. This gives you fine-grained control—you only enable PPR where it actually benefits you, avoiding unnecessary complexity in routes that don't need it.

The alternative is `ppr: true`, which would enable PPR globally, but incremental is safer for most projects since you're being intentional about which routes use this feature.

---

## Step 2: Export the PPR Experimental Flag from Your Route

Now, pick a specific route where you want to enable this behavior. I'll use the products page as an example:

```tsx
// app/products/page.tsx
export const experimental_ppr = true

export default function ProductsPage() {
  return <ProductsListing />
}
```

By exporting `experimental_ppr = true`, you're telling Next.js, "I want this route to be statically prerendered by default. Only the parts I explicitly mark as dynamic should wait for request-time data."

Without this export, the route either behaves fully static or fully dynamic depending on what code you use. With it, you're bridging the gap—you get a static shell that can be served instantly, and dynamic content streams in as needed.

---

## Step 3: Build Your Static Components Normally

The beauty of PPR is that the majority of your page stays completely normal—no special handling required. Here's the product listing component, which has no dependencies on request-time data:

```tsx
// app/products/ProductsListing.tsx
export default async function ProductsListing() {
  // This fetch is cached, so it's safe for static generation
  const products = await getAllProducts()

  return (
    <section>
      <h1>Products</h1>
      <div className="grid">
        {products.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </section>
  )
}
```

This component stays completely static during build time. The `getAllProducts()` call is cached (either through Next.js's built-in caching, ISR, or your own cache layer), so Next.js prerendered the entire component tree and serves it instantly to every visitor.

The key principle: as long as you're not calling `cookies()`, `headers()`, or `unstable_noStore()` inside a component, that component is safe to prerender. PPR only comes into play when you need request-time values.

---

## Step 4: Create Your Dynamic Component That Uses Cookies or Headers

Now for the part that would normally make your whole page dynamic. This is where you read the user's session cookie to personalize the experience:

```tsx
// app/products/UserBanner.tsx
import { cookies } from 'next/headers'

export async function UserBanner() {
  // This is request-time data—it changes per visitor
  const cookieStore = await cookies()
  const session = cookieStore.get('session')?.value

  if (!session) {
    return <div className="banner">Welcome, guest. <a href="/login">Sign in</a></div>
  }

  return (
    <div className="banner">
      Welcome back! <a href="/account">View your account</a>
    </div>
  )
}
```

In a traditional Next.js app, rendering this component anywhere in the tree would force the entire page to be dynamic. The page wouldn't be prerendered at build time—instead, the server would process every single request from scratch.

But with PPR and Suspense boundaries, this component can stay isolated. It will only execute when a request comes in, while the rest of the page (the `ProductsListing`) is already prerendered and ready to serve instantly.

---

## Step 5: Combine Them with Suspense

Here's the final piece: wrap the dynamic component in a Suspense boundary inside your main page:

```tsx
// app/products/page.tsx
import { Suspense } from 'react'
import ProductsListing from './ProductsListing'
import { UserBanner } from './UserBanner'

export const experimental_ppr = true

export default function ProductsPage() {
  return (
    <>
      <ProductsListing /> {/* Prerendered at build time */}
      <Suspense fallback={<div className="banner-skeleton">Loading your info…</div>}>
        <UserBanner /> {/* Deferred and streamed when request comes in */}
      </Suspense>
    </>
  )
}
```

This is where the magic happens. When Next.js builds this route with `experimental_ppr = true`:

1. It prerendered the `ProductsListing` component and everything above the Suspense boundary
2. It creates a static shell of the page (all HTML outside the Suspense boundary)
3. When a visitor requests the page, the server sends the static shell immediately while `UserBanner` processes in parallel
4. The user sees the products right away and the personalized banner streams in moments later
5. Meanwhile, fully static visitors (who don't need real-time personalization) get the static shell cached at the edge

The `fallback` inside Suspense is what users see while `UserBanner` is processing. It should be visually similar to what will be rendered—a skeleton loader works well here.

---

## The Real-World Impact

Before PPR, you faced an uncomfortable choice: keep the page fully static and show generic content to everyone, or use cookies/headers and accept that the entire page becomes dynamic. That meant no prerendering, no edge caching, slower initial load times for every visitor.

Now with PPR, you're getting the best of both worlds. Your static content (the product listing) is prerendered and cached globally. Your dynamic content (the personalized banner) only processes when needed. The user gets fast page loads, and you get the personalization you need.

The key takeaway is that PPR isn't about making your entire page dynamic or static—it's about being explicit with Suspense boundaries about which parts are which. The parts above the boundary are static and fast. The parts inside the boundary are dynamic and personalized.

Let me know in the comments if you've run into this challenge before, or if you have questions about implementing PPR in your own projects. And if you want to dive deeper into handling nested layouts or preventing child routes from inheriting dynamic rendering, let me know—I can write a follow-up guide.

Thanks,
Matija

## LLM Response Snippet
```json
{
  "goal": "Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today.",
  "responses": [
    {
      "question": "What does the article \"Master Next.js 16 Partial Prerendering in 5 Easy Steps\" cover?",
      "answer": "Master Next.js 16 with our guide on Partial Prerendering! Boost your e-commerce site speed and user personalization today."
    }
  ]
}
```