Getting started with Partial Prerendering
This guide walks through setting up Partial Prerendering (PPR) in a Next.js App Router project. Vercel serves the cached static shell from its CDN, then streams the dynamic parts from your function into the same response.
- A project deployed on Vercel
- A Next.js App Router project (PPR is available with Next.js)
As of Next.js 16, PPR is built into the Cache Components model. Opt in by enabling cacheComponents in your next.config.ts:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;With Cache Components, data is dynamic by default; you choose what to cache with the use cache directive.
Mark the content you want prerendered with use cache. This becomes the static shell:
type Post = { id: number; title: string };
async function Posts() {
'use cache';
// Cached: prerendered into the static shell
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json(),
);
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default function Page() {
return <Posts />;
}A page whose content is entirely cached prerenders into a full static shell. On a cache hit it is served from the CDN without invoking your function; your function still runs when the shell is regenerated. By default the shell revalidates on an interval, so set how long it stays fresh with cacheLife and invalidate it on demand with cacheTag.
Add per-request content and wrap it in a <Suspense> boundary:
import { Suspense } from 'react';
import { cookies } from 'next/headers';
type Post = { id: number; title: string };
async function Posts() {
'use cache';
// Cached: prerendered into the static shell
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json(),
);
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
async function Greeting() {
const user = (await cookies()).get('user')?.value;
return <p>Welcome back, {user}</p>;
}
export default function Page() {
return (
<main>
{/* Cached: prerendered into the static shell */}
<Posts />
{/* Dynamic: streamed in per request */}
<Suspense fallback={<p>Loading…</p>}>
<Greeting />
</Suspense>
</main>
);
}Keep per-request data inside a <Suspense> boundary. With Cache Components, reading uncached data like cookies() outside a boundary fails the build with an error about uncached data accessed outside a <Suspense> boundary, rather than silently rendering the whole route dynamically. The boundary is what lets Next.js prerender the static shell and stream the dynamic part into it.
- How PPR works: the request flow from build through revalidation
- Usage and pricing: the cost of PPR requests
Was this helpful?