export const experimental_ppr
Opted a route into experimental Partial Prerendering in Next.js 15; removed in Next.js 16.
export const experimental_ppr = true // true | false- Added in Next.js v15.0.0
- Removed in PHP v16.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$trueoptional | boolean literal | Opts the route segment, and all of its children including nested layouts and pages, into Partial Prerendering. Required experimental.ppr set to 'incremental' in next.config.js. |
$falseoptional | boolean literal | Disables Partial Prerendering for a child segment that would otherwise inherit it from a parent. Routes without the export default to false and are not prerendered with PPR. |
Changelog
- PHP v14.0.0experimental ppr introduced (the experimental.ppr config flag).
- PHP v15.0.0experimental 'incremental' value introduced, which is what enabled the experimental_ppr route segment export for per-route adoption.
- PHP v16.0.0export const experimental_ppr = true removed. Run npx @next/codemod@latest remove-experimental-ppr . to strip it. cacheComponents implements Partial Prerendering as the default App Router behaviour, so neither the experimental.ppr flag nor the segment export is necessary any more.
This export no longer exists. It is documented here because the name still turns up in search results, in Next.js 15 tutorials, and in codebases mid-upgrade.
What it did
In Next.js 15 with experimental.ppr set to 'incremental', adding export const experimental_ppr = true to a page or layout opted that route into Partial Prerendering: a static shell prerendered at build time, with anything wrapped in <Suspense> streamed in at request time. Routes without the export defaulted to false, so opting in was explicit and per route. The flag applied to every child of the segment, so it only went on the top segment of a route, and a child could set false to opt back out.
What to do now
Next.js 16 removed the flag and the export. Partial Prerendering is the default App Router behaviour once cacheComponents is enabled, so the migration is npx @next/codemod@latest remove-experimental-ppr . followed by adopting Cache Components.
The catch: PPR in 16 does not work the way it did in the 15 canaries. The documented advice for apps relying on it today is to stay on the Next.js 15 canary you are on and follow the Migrating to Cache Components guide rather than upgrading blind.
Example
// Next.js 15 only — removed in Next.js 16.
import { Suspense } from 'react'
export const experimental_ppr = true
export default function Page() {
return (
<>
<StaticHeader />
<Suspense fallback={<Skeleton />}>
<LiveOrders />
</Suspense>
</>
)
}
function StaticHeader() {
return <h1>Dashboard</h1>
}
function Skeleton() {
return <p>Loading orders…</p>
}
async function LiveOrders() {
const res = await fetch('https://api.example.com/orders', {
cache: 'no-store',
})
const orders: { id: string; total: number }[] = await res.json()
return (
<ul>
{orders.map((order) => (
<li key={order.id}>{order.total}</li>
))}
</ul>
)
}When to use it
- Recognise the export while upgrading a Next.js 15 project to 16 and remove it with the codemod.
- Understand a tutorial or a Stack Overflow answer that still tells you to add it.
- Translate a per-route PPR opt-in into the Cache Components model.
Frequently asked questions
Why does experimental_ppr no longer work in Next.js 16?
Next.js 16 removed both the experimental.ppr config flag and the experimental_ppr route segment export, because cacheComponents implements Partial Prerendering as the default App Router behaviour.
How do I remove experimental_ppr from my app?
Run npx @next/codemod@latest remove-experimental-ppr . which strips the export from App Router pages and layouts; the version 16 upgrade codemod also performs this step.
What replaced Partial Prerendering in Next.js 16?
Cache Components — enable cacheComponents in next.config and use the 'use cache' directive to choose what goes into the prerendered shell, with everything else dynamic by default. Note that PPR in 16 works differently from the Next.js 15 canaries, so read the Migrating to Cache Components guide before upgrading.
More examples
The Next.js 15 setup it required
The export alone did nothing: 'incremental' had to be set in next.config first. Placing it on the top segment covered the whole route, and false on a child was the documented way to exclude one page from an otherwise opted-in subtree.
// next.config.ts — Next.js 15
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
// app/dashboard/layout.tsx
// Applies to every nested layout and page under /dashboard.
export const experimental_ppr = true
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
// app/dashboard/settings/page.tsx
// A child can opt back out.
export const experimental_ppr = false
export default function SettingsPage() {
return <h1>Settings</h1>
}The Next.js 16 migration
cacheComponents implements Partial Prerendering as the default App Router behaviour, which is why both the experimental.ppr flag and the per-route export were removed. The upgrade codemod for version 16 runs the removal step as part of its normal pass.
// 1. Strip the export everywhere:
// npx @next/codemod@latest remove-experimental-ppr .
//
// app/dashboard/page.tsx
// - export const experimental_ppr = true
// 2. Opt into the replacement in next.config.ts:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfigThe same page under Cache Components
The route-level opt-in disappears and the decision moves into the components: 'use cache' marks what belongs in the prerendered shell, and everything else is dynamic by default and streams in behind a Suspense boundary.
// app/dashboard/page.tsx — Next.js 16 with cacheComponents: true
import { Suspense } from 'react'
// No segment export. The static shell is the default;
// what you cache is now decided per function.
export default function Page() {
return (
<>
<Header />
<Suspense fallback={<p>Loading orders…</p>}>
<LiveOrders />
</Suspense>
</>
)
}
async function Header() {
'use cache'
const res = await fetch('https://api.example.com/site')
const site: { title: string } = await res.json()
return <h1>{site.title}</h1>
}
async function LiveOrders() {
const res = await fetch('https://api.example.com/orders')
const orders: { id: string; total: number }[] = await res.json()
return (
<ul>
{orders.map((order) => (
<li key={order.id}>{order.total}</li>
))}
</ul>
)
}
Discussion