---
title: Partial Prerendering (PPR)
product: vercel
url: /docs/partial-prerendering
canonical_url: "https://vercel.com/docs/partial-prerendering"
last_updated: 2026-06-23
type: conceptual
prerequisites:
  []
related:
  - /docs/incremental-static-regeneration
  - /docs/functions
  - /docs/partial-prerendering/quickstart
  - /docs/caching/cdn-cache
  - /docs/regions
summary: Partial Prerendering serves a cached static shell instantly, then renders and streams the dynamic parts of a page per request.
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

# Partial Prerendering (PPR)

> **🔒 Permissions Required**: Partial Prerendering


<!-- docsgraph:related -->
## Related pages

> **For AI agents:** Follow these links to understand how this page connects to the rest of the Vercel ecosystem. For the full cross-link map (inbound, outbound, prerequisites, and semantic neighbors), see the .graph.md link below.

- [PPR Platform Guide](https://nextjs.org/docs/app/guides/ppr-platform-guide?from=related) — A guide for platform engineers on implementing PPR support, from basic origin rendering to optimized CDN integration.
- [How can I prerender my application on Vercel?](https://vercel.com/kb/guide/how-can-i-prerender-my-application-on-vercel?from=related) — Learn how to enable prerendering with your frontend framework on Vercel for better performance and SEO.
- [Implementing PPR in an Adapter](https://nextjs.org/docs/app/api-reference/adapters/implementing-ppr-in-an-adapter?from=related) — Implement Partial Prerendering support in an adapter using fallback output and cache hooks.
- [Deploying React with Vercel](https://vercel.com/kb/guide/deploying-react-with-vercel?from=related) — Deploy React with Vercel to replace your build pipeline and shared staging. See how framework detection, previews, and F
- [Data Cache](https://vercel.com/docs/caching/runtime-cache/data-cache?from=related) — Vercel Data Cache is a specialized cache that stores responses from data fetches in Next.js App Router
- [Overview](https://vercel.com/docs/cdn?from=related) — Vercel's CDN is a globally distributed platform that handles routing, caching, security, and compression for every deplo
- [Features](https://vercel.com/docs/build-output-api/features?from=related) — Learn how to implement common Vercel platform features through the Build Output API.
- [Getting Started](https://vercel.com/docs/incremental-static-regeneration/quickstart?from=related) — Learn how to set up Incremental Static Regeneration \\(ISR\\) with time-based and on-demand revalidation.
- [Create React App](https://vercel.com/docs/frameworks/frontend/create-react-app?from=related) — Learn how to use Vercel's features with Create React App

Full cross-link map for this page: [/docs/partial-prerendering.graph.md](/docs/partial-prerendering.graph.md)
<!-- /docsgraph:related -->

Partial Prerendering (PPR) is a rendering strategy that combines fast cached static content with the ability to render fresh dynamic content, all in one response. This strategy allows you to personalize pages while still having a performant site.

PPR requests have two parts to them: a static shell that is cached and served quickly, and a dynamic execution that fills in personalized content into the static shell. PPR uses [Incremental Static Regeneration (ISR)](/docs/incremental-static-regeneration) for the static shell of the page, and [Vercel Functions](/docs/functions) to render the dynamic parts of the page.

Use PPR when a page is mostly static but has a few parts that change per request or per user:

- **Product or catalog pages**: a static layout with a live price, inventory count, or cart contents.
- **Dashboards and app shells**: a cached layout with a personalized widget, such as a user greeting or a notification count.
- **Articles and marketing pages**: static content with personalized recommendations or an auth-aware header.

If your page is fully static, use ISR since there is no dynamic content.

To get started:

- Follow the [getting started guide](/docs/partial-prerendering/quickstart) to enable PPR in a Next.js App Router project
- [Set up PPR](#using-ppr) with your framework
- Learn [how PPR works](#how-ppr-works) from build time through revalidation
- Understand [usage and pricing](#usage-and-pricing)

## Benefits of Vercel's CDN for PPR

When you deploy PPR with your framework on Vercel, the CDN adds these optimizations:

- **Instant shell with dynamic content in one response**: Once a region has pulled the shell into its [CDN cache](/docs/caching/cdn-cache), Vercel serves it from the nearest of [Vercel's 20 regions](/docs/regions) for a fast Time to First Byte, while your function renders the dynamic parts and streams them into the same response.
- **Durable storage**: At deployment time, Vercel writes the shell to the [global ISR cache](/docs/incremental-static-regeneration/limits-and-pricing#isr-cache-region). On the first request in each region, Vercel pulls the shell into the regional [CDN cache](/docs/caching/cdn-cache). The shell persists in the global ISR cache until you revalidate it.
- **Cache shielding and request collapsing**: on a CDN cache miss, Vercel reads from the global ISR cache before invoking your function, and it [collapses](/docs/incremental-static-regeneration/request-collapsing) concurrent requests to the same path into one invocation per region.
- **Globally consistent purging**: when you revalidate content, all caches across all regions update within 300 milliseconds. Vercel purges HTML and data payloads together, so visitors see consistent content across full page loads and client-side transitions.
- **Instant rollbacks**: cached content from previous deployments isn't purged, so you can roll back without regenerating it.

## Using PPR

PPR is configured through your framework's APIs. Your framework code defines which parts of a route are static and which are dynamic, and Vercel handles the caching and function execution automatically.

Partial Prerendering is currently available with Next.js:

| Framework                | How to enable PPR                                          |
| ------------------------ | ---------------------------------------------------------- |
| **Next.js** (App Router) | Enable Cache Components and mark cached segments with `use cache` |

In Next.js, Cache Components and the `use cache` directive define the cached shell and the dynamic sections. You set how long cached content stays fresh with `cacheLife` and invalidate it on demand with `cacheTag`.

```tsx filename="app/page.tsx" framework=nextjs-app
import { Suspense } from 'react';
import { cacheTag } from 'next/cache';
import { cookies } from 'next/headers';

type Product = { id: string; name: string; price: number };

async function ProductsList() {
  'use cache';
  cacheTag('products-list');

  const products: Product[] = await fetch(
    'https://api.example.com/products',
  ).then((res) => res.json());
  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}

async function LoginState() {
  const user = (await cookies()).get('user')?.value;
  return <p>Welcome back, {user}</p>;
}

export default function Page() {
  return (
    <body>
      <h1>Product List Page</h1>

      {/* Dynamic: streamed in at request time */}
      <Suspense fallback={<p>Loading…</p>}>
        <LoginState />
      </Suspense>

      {/* Cached: prerendered into the static shell */}
      <ProductsList />
    </body>
  );
}
```

```jsx filename="app/page.js" framework=nextjs-app
import { Suspense } from 'react';
import { cacheTag } from 'next/cache';
import { cookies } from 'next/headers';

async function ProductsList() {
  'use cache';
  cacheTag('products-list');

  const products = await fetch('https://api.example.com/products').then((res) =>
    res.json(),
  );
  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}

async function LoginState() {
  const user = (await cookies()).get('user')?.value;
  return <p>Welcome back, {user}</p>;
}

export default function Page() {
  return (
    <body>
      <h1>Product List Page</h1>

      {/* Dynamic: streamed in at request time */}
      <Suspense fallback={<p>Loading…</p>}>
        <LoginState />
      </Suspense>

      {/* Cached: prerendered into the static shell */}
      <ProductsList />
    </body>
  );
}
```

For the full explanation and tutorial for how to use this, see [Cache Components](https://nextjs.org/docs/app/getting-started/caching). To cache data inside the dynamic sections separately from the page response, see [Runtime Cache](/docs/caching/runtime-cache).

## How PPR works

PPR executes at build time, request time, and through revalidation. The sections below walk through each stage.

### At build time

Your framework generates two things for each PPR route: a static shell with placeholders where dynamic content goes, and the data your function needs to resume rendering the dynamic parts. When you deploy, Vercel prepopulates the shell in the [global ISR cache](/docs/incremental-static-regeneration/limits-and-pricing#isr-cache-region).

### At request time

A request arrives at the nearest [Vercel region](/docs/regions). If the regional [CDN cache](/docs/caching/cdn-cache) already has the shell, Vercel serves it right away. If not, the first request in that region pulls the shell from the global ISR cache and populates the CDN cache. In both cases, your function renders the dynamic holes in parallel. The shell reaches the browser first. The function output streams into the same response as it completes, so dynamic content fills in after the initial paint.

This is the key difference from ISR: even when the shell is served from the CDN cache, your function still runs to render the dynamic holes. It is possible to have a fully cached page, but most PPR requests incur a function invocation.

### At revalidation time

Revalidation updates the cached shell in two steps. First, your function regenerates the shell in the background. Then, the next request in each region pulls the updated shell into the regional CDN cache.

Two triggers can update the cached content for a shell:

- **Time-based revalidation** runs automatically after a set interval
- **On-demand revalidation** runs when you call an API

After a revalidation, the next request will serve the last generated content and trigger an asynchronous background revalidation. On the next request, the new content will be served:

### On failure

If revalidation fails, Vercel keeps serving the existing shell from the global ISR cache and regional CDN caches, and retries shortly after. For the full failure behavior, see [how ISR works](/docs/incremental-static-regeneration#on-failure).

## Observability

You can observe how your PPR routes behave from the [**Observability**](https://vercel.com/d?to=%2F%5Bteam%5D%2F~%2Fobservability\&title=Try+Observability) tab in the Vercel dashboard.

The PPR observability page displays

- **Shell hit rate**: See the hit rates for the static shell portion of the page. A high shell hit rate means most visitors get an instant first paint.
- **Dynamic invocations**: The number of requests that required executing a function to fill in the dynamic portions of the page.

> **💡 Note:** PPR uses Incremental Static Regeneration (ISR) for the static shell portion of the page and Vercel Functions for the dynamic portion, so additional observability details are available from the ISR and Functions observability pages.

## Usage and pricing

When you use PPR with a framework on Vercel, you incur usage for:

- **[ISR reads and writes](/docs/incremental-static-regeneration/limits-and-pricing)**: Vercel reads the cached shell from the global ISR cache when the CDN cache misses, and writes fresh content back when it revalidates.
- **[Function invocations](/docs/functions/usage-and-pricing)**: your function runs to render the dynamic holes on most requests, plus any background revalidations.
- **[Fast Origin Transfer](/docs/manage-cdn-usage#fast-origin-transfer)**: rendered output transferred from your function to the CDN cache and global ISR cache.

## More resources

- [Partial Prerendering with Next.js](/docs/frameworks/full-stack/nextjs#partial-prerendering)
- [Incremental Static Regeneration (ISR)](/docs/incremental-static-regeneration)
- [Vercel Functions](/docs/functions)
- [Runtime Cache](/docs/caching/runtime-cache)


---

[View full sitemap](/docs/sitemap)
