Next.js 16 Partial Prerendering Patterns
Partial Prerendering (PPR) for the Next.js 16 App Router under the Cache Components model — the decisions PPR forces and how to settle them, written so an agent applies them while writing or reviewing code. Contains 21 rules across 6 categories, ordered from easy to complex: enable PPR → understand the static/dynamic boundary → cache → handle runtime data → compose whole pages → build forms and wizards. Each rule corrects a specific wrong default of a model defaulting to Next.js 14/15; there is no rule for things the model already gets right.
Version-specific. This skill targets Next.js 16 (PPR via
cacheComponents, React 19.2). The Next.js 14/15experimental.pprflag andexport const experimental_pprroute export were removed — seesetup-enable-cache-components. For migrating an existing app, see the migration guide.
Write, then verify. These rules are for authoring PPR; they can't tell you what actually rendered. To empirically deconstruct the boundary — diff the static shell against the hydrated DOM to find the dynamic holes, locate the
'use client'islands, measure loading, and explain why a route is dynamic — drivenext buildand a real browser per _debug-boundaries.md.
When to Apply
- Building or reviewing a Next.js 16 page that mixes static chrome with personalized, real-time, or per-request content
- Enabling or migrating PPR (
cacheComponents), or seeing deadexperimental.ppr/experimental_pprcode - Deciding where
<Suspense>boundaries go, or debugging anUncached data was accessed outside of <Suspense>build error - Adding
'use cache',cacheLife,cacheTag, or choosingupdateTag/revalidateTag/refreshafter a mutation - Composing forms, multi-step wizards, dashboards, or streaming server data into interactive Client Components
- Empirically verifying or debugging what actually rendered — which parts are in the static shell vs streamed, where the CSR/SSR boundary is, and why a route went dynamic
Rule Categories
| # | Category | Prefix | Covers |
|---|---|---|---|
| 1 | Setup & Mental Model | setup- |
Enabling PPR with cacheComponents; the removed experimental flags; dynamic-by-default / opt-in caching inversion |
| 2 | The Suspense Boundary | shell- |
<Suspense> as the static/dynamic seam; the build error; boundary granularity; what Suspense does not do |
| 3 | Caching with 'use cache' |
cache- |
Directive levels; automatic keys; runtime values as props; pass-through; cacheLife/cacheTag; serverless durability |
| 4 | Runtime APIs & Non-Determinism | runtime- |
Async request APIs forcing a boundary; generateStaticParams; connection() for randomness/time |
| 5 | Page Composition Recipes | compose- |
Single hole → parallel dashboard → Promise + use() streaming → not opting the whole app out of the shell |
| 6 | Forms, Mutations & Wizards | mutate- |
updateTag vs revalidateTag vs refresh; URL-driven wizard steps; <Activity> field preservation |
Quick Reference
1. Setup & Mental Model
setup-enable-cache-components— Enable PPR viacacheComponents: true; theexperimental.ppr/experimental_pprflags are removedsetup-dynamic-by-default— Everything renders at request time; caching is opt-in via'use cache'(andfetchis no longer cached)
2. The Suspense Boundary
shell-suspense-is-the-boundary—<Suspense>is the static-shell/dynamic-stream seam, not a spinnershell-wrap-uncached-data— Uncached/runtime reads must be wrapped (or'use cache'd) or the build errorsshell-suspense-does-not-force-dynamic— Suspense alone does not make synchronous work dynamicshell-place-boundaries-low— Wrap the dynamic leaf, not the whole page, so the shell stays large
3. Caching with 'use cache'
cache-use-cache-directive— Mark static/cacheable work at the function / component / page / layout / file levelcache-keys-are-automatic— Arguments and closures form the cache key; pass varying inputs as argscache-pass-runtime-values-as-props— You can't readcookies()/headers()inside a cached scope; pass values incache-pass-through-children-and-actions— Pass dynamicchildrenand Server Actions through a cached component untouchedcache-set-lifetime-and-tags—cacheLifecontrols TTL,cacheTagenables on-demand invalidationcache-in-memory-not-durable-serverless— In-memory cache isn't durable on serverless; use'use cache: remote'
4. Runtime APIs & Non-Determinism
runtime-request-apis-force-a-boundary— Asynccookies/headers/searchParams/paramsforce a dynamic boundaryruntime-keep-param-routes-static—generateStaticParamskeeps[slug]routes in the static shellruntime-gate-nondeterminism-with-connection— GateMath.random/Date.now/cryptobehindconnection(), or cache the value
5. Page Composition Recipes
compose-single-dynamic-hole— The baseline: static shell + one<Suspense>holecompose-parallel-holes— One boundary per widget → parallel streaming, no waterfallcompose-stream-to-client-with-use— Pass an un-awaited Promise and unwrap withuse()in a Client Componentcompose-do-not-opt-out-the-shell— Don't defer the whole app to silence a boundary error
6. Forms, Mutations & Wizards
mutate-updatetag-vs-revalidatetag—updateTag(read-your-writes) vsrevalidateTag(tag, profile)(SWR) vsrefresh()mutate-wizard-url-driven-steps— URL-driven steps + static chrome +<Activity>field preservation
How to Use
Read a reference file when its decision comes up. Each rule names the wrong default it corrects, then shows the canonical way (with an incorrect/correct contrast only where the wrong way is a real trap). If you're starting cold, read setup- first — the rest assumes the dynamic-by-default mental model.
- Section definitions — category structure and ordering
- Boundary debugging — empirically deconstruct the static/dynamic boundary and loading with
next buildand chrome-devtools-mcp (via mcporter); use it when a PPR result surprises you or you're chasing ablocking-routeerror - Rule template — for adding new rules
- AGENTS.md — auto-built table of contents across all rules
Related Skills
nextjs— broader Next.js 16 App Router best practices (caching, server components, routing, hygiene)opinionated-nextjs-patterns— full opinionated architecture (data layer, mutations, client boundaries) that uses these PPR patternsreact-fetch-cache-patterns— request orchestration and client-side caching for data-heavy React UIs
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| references/_debug-boundaries.md | Empirical CSR/SSR boundary & loading debugging (next build + chrome-devtools-mcp via mcporter) |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and source references |