Next Cache Components

by vercel882e66c26986No licenseListed Oct 8, 2026Updated Oct 8, 2026

Next.js 16 Cache Components guidance — PPR, use cache directive, cacheLife, cacheTag, updateTag, and migration from unstable_cache. Use when implementing partial prerendering, caching strategies, or migrating from older Next.js cache patterns.

Instructions onlySoftware Development
AI-generated overview

Guides Next.js 16 Cache Components: PPR, the use cache directive, cacheLife, cacheTag, and migration from unstable_cache.

What it does
Provides reference guidance for Next.js 16 Cache Components, covering Partial Prerendering, the use cache directive at file, component and function level, and cache profiles. It explains cacheLife lifetimes, cacheTag tagging, updateTag and revalidateTag invalidation, runtime data constraints, and automatic cache key generation. It also documents migration from older patterns such as unstable_cache, experimental.ppr, and legacy revalidate settings.
When to use it
Use when implementing partial prerendering or caching strategies in a Next.js 16 app. Use when migrating from unstable_cache, experimental.ppr, or older revalidate and dynamic configurations. Use when debugging cache invalidation with cacheTag, updateTag, or revalidateTag.
Requirements
No scripts; instructions only. Assumes a Next.js 16 project with cacheComponents enabled and Node.js runtime, since edge runtime and static export are not supported.

Cache Components (Next.js 16+)

Cache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route.

Enable Cache Components

ts
// next.config.tsimport type { NextConfig } from 'next'
const nextConfig: NextConfig = {  cacheComponents: true,}
export default nextConfig

This replaces the old experimental.ppr flag.


Three Content Types

With Cache Components enabled, content falls into three categories:

1. Static (Auto-Prerendered)

Synchronous code, imports, pure computations - prerendered at build time:

tsx
export default function Page() {  return (    <header>      <h1>Our Blog</h1>  {/* Static - instant */}      <nav>...</nav>    </header>  )}

2. Cached (use cache)

Async data that doesn't need fresh fetches every request:

tsx
async function BlogPosts() {  'use cache'  cacheLife('hours')
  const posts = await db.posts.findMany()  return <PostList posts={posts} />}

3. Dynamic (Suspense)

Runtime data that must be fresh - wrap in Suspense:

tsx
import { Suspense } from 'react'
export default function Page() {  return (    <>      <BlogPosts />  {/* Cached */}
      <Suspense fallback={<p>Loading...</p>}>        <UserPreferences />  {/* Dynamic - streams in */}      </Suspense>    </>  )}
async function UserPreferences() {  const theme = (await cookies()).get('theme')?.value  return <p>Theme: {theme}</p>}

use cache Directive

File Level

tsx
'use cache'
export default async function Page() {  // Entire page is cached  const data = await fetchData()  return <div>{data}</div>}

Component Level

tsx
export async function CachedComponent() {  'use cache'  const data = await fetchData()  return <div>{data}</div>}

Function Level

tsx
export async function getData() {  'use cache'  return db.query('SELECT * FROM posts')}

Cache Profiles

Built-in Profiles

tsx
'use cache'                    // Default: 5m stale, 15m revalidate
tsx
'use cache: remote'           // Platform-provided cache (Redis, KV)
tsx
'use cache: private'          // For compliance, allows runtime APIs

cacheLife() - Custom Lifetime

tsx
import { cacheLife } from 'next/cache'
async function getData() {  'use cache'  cacheLife('hours')  // Built-in profile  return fetch('/api/data')}

Built-in profiles: 'default', 'minutes', 'hours', 'days', 'weeks', 'max'

Inline Configuration

tsx
async function getData() {  'use cache'  cacheLife({    stale: 3600,      // 1 hour - serve stale while revalidating    revalidate: 7200, // 2 hours - background revalidation interval    expire: 86400,    // 1 day - hard expiration  })  return fetch('/api/data')}

Cache Invalidation

cacheTag() - Tag Cached Content

tsx
import { cacheTag } from 'next/cache'
async function getProducts() {  'use cache'  cacheTag('products')  return db.products.findMany()}
async function getProduct(id: string) {  'use cache'  cacheTag('products', `product-${id}`)  return db.products.findUnique({ where: { id } })}

updateTag() - Immediate Invalidation

Use when you need the cache refreshed within the same request:

tsx
'use server'
import { updateTag } from 'next/cache'
export async function updateProduct(id: string, data: FormData) {  await db.products.update({ where: { id }, data })  updateTag(`product-${id}`)  // Immediate - same request sees fresh data}

revalidateTag() - Background Revalidation

Use for stale-while-revalidate behavior:

tsx
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost(data: FormData) {  await db.posts.create({ data })  revalidateTag('posts')  // Background - next request sees fresh data}

Runtime Data Constraint

Cannot access cookies(), headers(), or searchParams inside use cache.

Solution: Pass as Arguments

tsx
// Wrong - runtime API inside use cacheasync function CachedProfile() {  'use cache'  const session = (await cookies()).get('session')?.value  // Error!  return <div>{session}</div>}
// Correct - extract outside, pass as argumentasync function ProfilePage() {  const session = (await cookies()).get('session')?.value  return <CachedProfile sessionId={session} />}
async function CachedProfile({ sessionId }: { sessionId: string }) {  'use cache'  // sessionId becomes part of cache key automatically  const data = await fetchUserData(sessionId)  return <div>{data.name}</div>}

Exception: use cache: private

For compliance requirements when you can't refactor:

tsx
async function getData() {  'use cache: private'  const session = (await cookies()).get('session')?.value  // Allowed  return fetchData(session)}

Cache Key Generation

Cache keys are automatic based on:

  • Build ID - invalidates all caches on deploy
  • Function ID - hash of function location
  • Serializable arguments - props become part of key
  • Closure variables - outer scope values included
tsx
async function Component({ userId }: { userId: string }) {  const getData = async (filter: string) => {    'use cache'    // Cache key = userId (closure) + filter (argument)    return fetch(`/api/users/${userId}?filter=${filter}`)  }  return getData('active')}

Complete Example

tsx
import { Suspense } from 'react'import { cookies } from 'next/headers'import { cacheLife, cacheTag } from 'next/cache'
export default function DashboardPage() {  return (    <>      {/* Static shell - instant from CDN */}      <header><h1>Dashboard</h1></header>      <nav>...</nav>
      {/* Cached - fast, revalidates hourly */}      <Stats />
      {/* Dynamic - streams in with fresh data */}      <Suspense fallback={<NotificationsSkeleton />}>        <Notifications />      </Suspense>    </>  )}
async function Stats() {  'use cache'  cacheLife('hours')  cacheTag('dashboard-stats')
  const stats = await db.stats.aggregate()  return <StatsDisplay stats={stats} />}
async function Notifications() {  const userId = (await cookies()).get('userId')?.value  const notifications = await db.notifications.findMany({    where: { userId, read: false }  })  return <NotificationList items={notifications} />}

Migration from Previous Versions

Old ConfigReplacement
experimental.pprcacheComponents: true
dynamic = 'force-dynamic'Remove (default behavior)
dynamic = 'force-static''use cache' + cacheLife('max')
revalidate = NcacheLife({ revalidate: N })
unstable_cache()'use cache' directive

Migrating unstable_cache to use cache

unstable_cache has been replaced by the use cache directive in Next.js 16. When cacheComponents is enabled, convert unstable_cache calls to use cache functions:

Before (unstable_cache):

tsx
import { unstable_cache } from 'next/cache'
const getCachedUser = unstable_cache(  async (id) => getUser(id),  ['my-app-user'],  {    tags: ['users'],    revalidate: 60,  })
export default async function Page({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params  const user = await getCachedUser(id)  return <div>{user.name}</div>}

After (use cache):

tsx
import { cacheLife, cacheTag } from 'next/cache'
async function getCachedUser(id: string) {  'use cache'  cacheTag('users')  cacheLife({ revalidate: 60 })  return getUser(id)}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params  const user = await getCachedUser(id)  return <div>{user.name}</div>}

Key differences:

  • No manual cache keys - use cache generates keys automatically from function arguments and closures. The keyParts array from unstable_cache is no longer needed.
  • Tags - Replace options.tags with cacheTag() calls inside the function.
  • Revalidation - Replace options.revalidate with cacheLife({ revalidate: N }) or a built-in profile like cacheLife('minutes').
  • Dynamic data - unstable_cache did not support cookies() or headers() inside the callback. The same restriction applies to use cache, but you can use 'use cache: private' if needed.

Limitations

  • Edge runtime not supported - requires Node.js
  • Static export not supported - needs server
  • Non-deterministic values (Math.random(), Date.now()) execute once at build time inside use cache

For request-time randomness outside cache:

tsx
import { connection } from 'next/server'
async function DynamicContent() {  await connection()  // Defer to request time  const id = crypto.randomUUID()  // Different per request  return <div>{id}</div>}

Sources:

Source and attribution

Source:vercel/vercel-plugininskills/next-cache-componentsat commit882e66c

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal