Development5 min read

Next.js 16 Partial Prerendering (PPR): Mastering the new Component Caching Model

Share Article:

Introduction

Partial Prerendering (PPR) has been one of the most anticipated architectural shifts in web engineering since Next.js introduced Server Components.

For years, developers had to choose between Static Site Generation (SSG)—which provides fast, edge-cached delivery but struggles with dynamic content—and Server-Side Rendering (SSR), which handles user-specific data at the cost of high Time-to-First-Token (TTFT) and server database round-trips.

With the release of Next.js 16, PPR is finally stable.

Next.js 16 completely strips away the legacy experimental flags and introduces the stable cacheComponents configuration. This shift introduces a new component-level caching model, moving away from route-level segments and bringing dynamic edge streaming to production web architectures.

In this deep dive, we explore how to configure the new caching model, use the use cache directive, and optimize edge CDN TTL parameters.

PPR Architecture

Understanding the Next.js 16 Cache Model

In previous experimental iterations of Next.js, PPR was enabled using route segment configurations like experimental_ppr. This approach lacked granular control; you either enabled PPR for a whole route directory or bypassed it completely.

Next.js 16 changes this by making PPR a default behavior when cacheComponents: true is configured in next.config.ts.

// next.config.ts (Next.js 16 Stable Configuration)
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  cacheComponents: true, // Enables stable component-level caching and PPR
};
 
export default nextConfig;

Under this configuration, Next.js generates a Static Shell at build time. The static layout (e.g., navbars, footers, headers) is cached immediately at the Edge CDN. When a user requests the page, the CDN serves this shell instantly, dropping TTFT to near-zero.

Dynamic segments, wrapped in React <Suspense> boundaries, are resolved on the server and streamed down to the browser as dynamic "holes" in the document, completely bypassing full-page dynamic rendering delays.

The use cache Directive and cacheLife

To control the lifespan of cached data inside your static shell, Next.js 16 introduces the use cache directive and the cacheLife helper. Instead of relying on traditional route revalidation timers, you can now define cache policies directly inside your components or data-fetching functions.

// src/components/ProductGrid.tsx
import { cacheLife } from 'next/cache';
 
async function fetchProducts() {
  'use cache';
  cacheLife('hourly'); // Applies a pre-defined cache lifecycle profile
 
  const res = await fetch('https://api.habitwala.in/products');
  return res.json();
}
 
export default async function ProductGrid() {
  const products = await fetchProducts();
  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map((p: any) => (
        <div key={p.id} className="border p-4">{p.title}</div>
      ))}
    </div>
  );
}

The 'use cache' directive instructs the compiler to capture the return value of the function and cache it. The cacheLife('hourly') call applies a specific cache profile defined in your configurations.

Defining Custom Cache Profiles

You can define custom caching lifecycles in your next.config.ts using the cacheProfiles schema:

// next.config.ts
const nextConfig: NextConfig = {
  cacheComponents: true,
  cacheLife: {
    profiles: {
      hourly: {
        stale: 3600,    // 1 hour fresh period
        expire: 86400,  // 24 hours total expiration
      },
      realtime: {
        stale: 0,       // Must revalidate on every request
        expire: 60,     // Expire completely after 1 minute
      }
    }
  }
};

This component-level granularity lets you co-locate static and dynamic data fetching logic without worrying about route-wide caching interference.

Edge CDN Caching & Stream Synchronization

When a Next.js 16 page with PPR is requested, the network request undergoes a two-step delivery process:

  1. Static Delivery: The Edge CDN instantly serves the prerendered HTML shell cached with a standard Cache-Control header (e.g., s-maxage=604800, stale-while-revalidate=86400).

  2. Server Streaming: The browser keeps the HTTP connection open. The server resolves the <Suspense> boundaries, generates the dynamic chunks, and streams them over the same connection.

Edge CDN Low Latency

This hybrid architecture requires strict synchronization between your Edge CDN (e.g., Vercel CDN, Cloudflare) and the Next.js origin server.

Because the static shell is served instantly, traditional cache-control headers on the page request must only target the static shell. Next.js handles this by setting specialized streaming headers that instruct CDNs to cache the shell but bypass caching for the streamed chunks.

Handling Invalidation

For surgical cache invalidation, Next.js 16 relies on dynamic tags. You can assign cache tags to components or functions and invalidate them on-demand:

import { cacheTag } from 'next/cache';
 
async function fetchProductDetails(id: string) {
  'use cache';
  cacheTag(`product-${id}`); // Tag the cache block
  
  return fetch(`https://api.habitwala.in/products/${id}`).then(res => res.json());
}

When a product is updated in your CMS, you can invoke a Server Action to clear that specific component's cache across all edge locations instantly:

'use server';
import { revalidateTag } from 'next/cache';
 
export async function updateProduct(id: string, data: any) {
  await database.update(id, data);
  revalidateTag(`product-${id}`); // Invalidates only the cached product component
}

This replaces full-page regenerations and cache-purging, keeping your page delivery fast and dynamic.

Conclusion

Stable Partial Prerendering in Next.js 16 represents a massive architectural advancement. By shifting caching from the route level to the component level with cacheComponents, the framework provides granular control over static and dynamic assets.

By combining the 'use cache' directive, custom cacheLife profiles, and surgical revalidateTag calls, you can build web applications that achieve sub-millisecond edge load times while maintaining live, dynamic data streaming.

If you are upgrading to Next.js 16, start refactoring your dynamic routes to leverage component-level caching and migrate away from legacy experimental segments. Your Core Web Vitals will thank you.

A

Written by Aman Janwani

Founder & Lead Security Architect

Building ultra-fast, local-first security infrastructure and developer tooling for AI agents. Focused on zero-overhead protection and high-performance Web architecture.

Recommended Technical Reads