Partial Prerendering
Partial Prerendering (PPR) is a rendering strategy that allows you to combine static and dynamic content in the same route. This improves the initial page performance while still supporting personalized, dynamic data.


When a user visits a route:
- The server sends ashell containing the static content, ensuring a fast initial load.
- The shell leavesholes for the dynamic content that will load in asynchronously.
- The dynamic holes arestreamed in parallel, reducing the overall load time of the page.
🎥 Watch: Why PPR and how it works →YouTube (10 minutes).
How does Partial Prerendering work?
To understand Partial Prerendering, it helps to be familiar with the rendering strategies available in Next.js.
Static Rendering
With Static Rendering, HTML is generated ahead of time—either at build time or throughrevalidation. The result is cached and shared across users and requests.
In Partial Prerendering, Next.js prerenders astatic shell for a route. This can include the layout and any other components that don't depend on request-time data.
Dynamic Rendering
With Dynamic Rendering, HTML is generated atrequest time. This allows you to serve personalized content based on request-time data.
A component becomes dynamic if it uses the following APIs:
cookies
headers
connection
draftMode
searchParams
propunstable_noStore
fetch
with{ cache: 'no-store' }
In Partial Prerendering, using these APIs throws a special React error that informs Next.js the component cannot be statically rendered, causing a build error. You can use aSuspense boundary to wrap your component to defer rendering until runtime.
Suspense
ReactSuspense is used to defer rendering parts of your application until some condition is met.
In Partial Prerendering, Suspense is used to markdynamic boundaries in your component tree.
At build time, Next.js prerenders the static content and thefallback
UI. The dynamic content ispostponed until the user requests the route.
Wrapping a component in Suspense doesn't make the component itself dynamic (your API usage does), but rather Suspense is used as a boundary that encapsulates dynamic content and enablestreaming
import { Suspense }from'react'import StaticComponentfrom'./StaticComponent'import DynamicComponentfrom'./DynamicComponent'import Fallbackfrom'./Fallback'exportconstexperimental_ppr=trueexportdefaultfunctionPage() {return ( <> <StaticComponent /> <Suspensefallback={<Fallback />}> <DynamicComponent /> </Suspense> </> )}
Streaming
Streaming splits the route into chunks and progressively streams them to the client as they become ready. This allows the user to see parts of the page immediately, before the entire content has finished rendering.


In Partial Prerendering, dynamic components wrapped in Suspense start streaming from the server in parallel.


To reduce network overhead, the full response—including static HTML and streamed dynamic parts—is sent in asingle HTTP request. This avoids extra roundtrips and improves both initial load and overall performance.
Enabling Partial Prerendering
You can enable PPR by adding theppr
option to yournext.config.ts
file:
importtype { NextConfig }from'next'constnextConfig:NextConfig= { experimental: { ppr:'incremental', },}exportdefault nextConfig
The'incremental'
value allows you to adopt PPR for specific routes:
exportconstexperimental_ppr=trueexportdefaultfunctionLayout({ children }: { children:React.ReactNode }) {// ...}
Routes that don't haveexperimental_ppr
will default tofalse
and will not be prerendered using PPR. You need to explicitly opt-in to PPR for each route.
Good to know:
experimental_ppr
will apply to all children of the route segment, including nested layouts and pages. You don't have to add it to every file, only the top segment of a route.- To disable PPR for children segments, you can set
experimental_ppr
tofalse
in the child segment.
Examples
Dynamic APIs
When using Dynamic APIs that require looking at the incoming request, Next.js will opt into dynamic rendering for the route. To continue using PPR, wrap the component with Suspense. For example, the<User />
component is dynamic because it uses thecookies
API:
import { cookies }from'next/headers'exportasyncfunctionUser() {constsession= (awaitcookies()).get('session')?.valuereturn'...'}
The<User />
component will be streamed while any other content inside<Page />
will be prerendered and become part of the static shell.
import { Suspense }from'react'import { User, AvatarSkeleton }from'./user'exportconstexperimental_ppr=trueexportdefaultfunctionPage() {return ( <section> <h1>This will be prerendered</h1> <Suspensefallback={<AvatarSkeleton />}> <User /> </Suspense> </section> )}
Passing dynamic props
Components only opt into dynamic rendering when the value is accessed. For example, if you are readingsearchParams
from a<Page />
component, you can forward this value to another component as a prop:
import { Table, TableSkeleton }from'./table'import { Suspense }from'react'exportdefaultfunctionPage({ searchParams,}: { searchParams:Promise<{ sort:string }>}) {return ( <section> <h1>This will be prerendered</h1> <Suspensefallback={<TableSkeleton />}> <TablesearchParams={searchParams} /> </Suspense> </section> )}
Inside of the table component, accessing the value fromsearchParams
will make the component dynamic while the rest of the page will be prerendered.
exportasyncfunctionTable({ searchParams,}: { searchParams:Promise<{ sort:string }>}) {constsort= (await searchParams).sort==='true'return'...'}
Next Steps
Was this helpful?