Skip to content
App RouterGetting StartedPartial Prerendering

How to use Partial Prerendering

This feature is currently experimental and subject to change, it's not recommended for production. Try it out and share your feedback on GitHub.

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.

Partially Prerendered Product Page showing static nav and product information, and dynamic cart and recommended products

When a user visits a route:

  • The server sends a shell containing the static content, ensuring a fast initial load.
  • The shell leaves holes for the dynamic content that will load in asynchronously.
  • The dynamic holes are streamed 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?

Partial Prerendering uses React's Suspense to defer rendering parts of your application until some condition is met.

The Suspense fallback is embedded into the initial HTML along with the static content. At build time (or during revalidation), the static content and fallback are prerendered to create a static shell. The rendering of dynamic content is postponed until the user requests the route.

Wrapping a component in Suspense doesn't make the component itself dynamic, but rather Suspense is used as a boundary that encapsulates dynamic content.

app/page.js
import { Suspense } from 'react'
import StaticComponent from './StaticComponent'
import DynamicComponent from './DynamicComponent'
import Fallback from './Fallback'
 
export const experimental_ppr = true
 
export default function Page() {
  return (
    <>
      <StaticComponent />
      <Suspense fallback={<Fallback />}>
        <DynamicComponent />
      </Suspense>
    </>
  )
}

To avoid client-server waterfalls, dynamic components start streaming from the server in parallel with the static prerender. This allows them to begin rendering before the browser loads client-side JavaScript.

To reduce network overhead, PPR sends both static and dynamic content in a single HTTP response, avoiding extra roundtrips for each dynamic component.

Enabling Partial Prerendering

You can enable PPR by adding the ppr option to your next.config.ts file:

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}
 
export default nextConfig

The 'incremental' value allows you to adopt PPR for specific routes:

/app/dashboard/layout.tsx
export const experimental_ppr = true
 
export default function Layout({ children }: { children: React.ReactNode }) {
  // ...
}
/app/dashboard/layout.js
export const experimental_ppr = true
 
export default function Layout({ children }) {
  // ...
}

Routes that don't have experimental_ppr will default to false 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 to false 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 the cookies API:

app/user.tsx
import { cookies } from 'next/headers'
 
export async function User() {
  const session = (await cookies()).get('session')?.value
  return '...'
}

The <User /> component will be streamed while any other content inside <Page /> will be prerendered and become part of the static shell.

app/page.tsx
import { Suspense } from 'react'
import { User, AvatarSkeleton } from './user'
 
export const experimental_ppr = true
 
export default function Page() {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Suspense fallback={<AvatarSkeleton />}>
        <User />
      </Suspense>
    </section>
  )
}

Passing dynamic props

Components only opt into dynamic rendering when the value is accessed. For example, if you are reading searchParams from a <Page /> component, you can forward this value to another component as a prop:

app/page.tsx
import { Table, TableSkeleton } from './table'
import { Suspense } from 'react'
 
export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  return (
    <section>
      <h1>This will be prerendered</h1>
      <Suspense fallback={<TableSkeleton />}>
        <Table searchParams={searchParams} />
      </Suspense>
    </section>
  )
}

Inside of the table component, accessing the value from searchParams will make the component dynamic while the rest of the page will be prerendered.

app/table.tsx
export async function Table({
  searchParams,
}: {
  searchParams: Promise<{ sort: string }>
}) {
  const sort = (await searchParams).sort === 'true'
  return '...'
}

Next Steps

Learn more about the config option for Partial Prerendering.