"The holy grail of web performance: Instant static shell + Streaming dynamic content."
(A minimal reproduction of Next.js 16 Partial Prerendering behavior)
This project is a minimal, production-ready demonstration of Partial Prerendering (PPR) in Next.js 16, designed to showcase the new Cache Components architecture without the complexity of a full app.
It visually demonstrates the difference between:
- ⚡ Static Shell Rendering: Layouts and headers load instantly (Build Time).
- ⏳ Dynamic Streaming: Critical data streams in parallel (Request Time).
git clone [https://github.com/ashishgogula/nextjs-16-ppr-demo.git](https://github.com/ashishgogula/nextjs-16-ppr-demo.git)
cd nextjs-16-ppr-demo
npm installNote: PPR behavior is best observed in a production build, as next dev does not fully emulate static shell serving.
npm run build
npm startVisit http://localhost:3000/ppr to see the instant shell + streaming data in action.
This demo uses the new Cache Components architecture in Next.js 16, which unifies Partial Prerendering and Caching into a single configuration.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true, // Unlocks PPR and 'use cache'
};
export default nextConfig;/ppr: The main demo route.- Static Shell:
layout.tsx+<h1>headers (Prerendered). - Dynamic Hole:
<SlowData />component wrapped in<Suspense>.
- Static Shell:
/api/slow: A simulated delay endpoint (5s) to guarantee predictable "slow" data for testing.
app/
api/
slow/
route.ts # Dynamic API route with intentional 5s delay
ppr/
page.tsx # The PPR Route (Static Shell + Dynamic Hole)
layout.tsx # Static layout (Critical for shell rendering)
components/
SlowData.tsx # Async Server Component (Fetches data)
Skeleton.tsx # Fallback UI (displayed instantly)
lib/
slowFetch.ts # Helper to fetch with { cache: 'no-store' }To ensure PPR is actually enabled, this demo avoids common de-optimizations that force full dynamic rendering:
- ✅ No Async Layouts:
layout.tsxis kept synchronous to allow the shell to generate at build time. - ✅ No Cookie/Header Access in Layouts: We strictly avoid request-time data in the static root.
- ✅ Granular Caching: We use
cache: 'no-store'inside the component (SlowData), rather than forcing the entire route to be dynamic. - ✅ Suspense Boundaries: Dynamic regions are strictly isolated inside
<Suspense>to unblock the rest of the UI.
- Fork this repository.
- Import it to Vercel.
- Deploy (Next.js 16 preset is automatic).
Once deployed, the static shell will be served from the Edge, while the dynamic content streams from a Server Function.
This project is open source and available under the MIT License.
Built by Ashish Gogula