Partial Prerendering (PPR): Static Shell & Dynamic Streaming
- Last updated
- Prerequisites:
- React Suspense
- Next.js App Router static vs dynamic rendering
- nextjs
- ppr
- streaming
- suspense
- performance
- app-router
Read at your depth
The practical view
Partial Prerendering (PPR) lets a single route combine static prerendering and dynamic streaming. Everything outside a `<Suspense>` boundary is built statically at compile time and cached on the CDN edge. Components wrapped in `<Suspense>` containing dynamic calls (like `cookies()`, `headers()`, or uncached `fetch()`) stream in parallel as HTML chunks after the initial shell arrives. Enable it in `next.config.js` via `experimental.ppr = true`.
The same idea in other frameworks
Combines `prerender` / `renderToPipeableStream` with fallback markers.SSR route prerendering with `@defer (on immediate)` placeholders.Nuxt hybrid rendering (`routeRules`) with granular Suspense components.SvelteKit streaming responses with deferred `{#await}` promises.Legacy vs modern
All-or-nothing SSR vs Partial Prerendering
Traditional dynamic SSR forces the entire route to wait on the server; PPR delivers a static layout immediately while streaming dynamic segments.
export const dynamic = 'force-dynamic'; // ❌ OLD: Entire page blocks on dynamic callsexport const experimental_ppr = true; // ✅ NEW: Instant shell + streamed dynamic holeInterview gotchas
Context
Interviewers test whether you understand how Next.js determines boundary constraints during build-time compilation.
The mechanical answer
If a dynamic function like `cookies()`, `headers()`, or `searchParams` is invoked outside a `<Suspense>` boundary in a PPR-enabled route, the build process cannot isolate the dynamic execution. Consequently, the entire route bails out of static generation and falls back to full dynamic server rendering on every request.
Trap
Do not claim that Next.js automatically wraps un-suspended components in Suspense. Uncaught dynamic reads in the outer tree invalidate the static shell completely.
Context
This checks your understanding of edge network behavior and HTTP streaming.
The mechanical answer
With PPR, the initial response chunk (the static shell) is cached directly at the CDN/Edge. When a client requests the page, the Edge immediately returns HTTP 200 with the static shell bytes (delivering single-digit millisecond TTFB) and initiates an upstream connection to the serverless origin to stream the remaining dynamic chunks over chunked transfer encoding.
Trap
Avoid stating that the dynamic payload is fetched via a client-side `fetch()`. It is streamed over the same initial HTTP connection as part of the React Flight payload.