Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
vercel-labs avatar

Next Cache Components

  • 464 installs
  • 1.5k repo stars
  • Updated March 6, 2026
  • vercel-labs/openreview

This is a copy of next-cache-components by vercel-labs - installs and ranking accrue to the original listing.

next-cache-components is an agent skill that configures Next.js 16 Cache Components, PPR, use cache, cacheLife, cacheTag, and updateTag.

About

next-cache-components is an agent skill focused on Next.js 16+ Cache Components, which implement Partial Prerendering by combining static shells, cached async segments, and dynamic streaming in a single route. Solo builders who outgrew all-static or all-dynamic pages use it when blog lists, marketing chrome, and personalized widgets must coexist without sacrificing TTFB or freshness. The skill explains flipping cacheComponents on in next.config.ts, marking async server components with the use cache directive alongside cacheLife, and wrapping runtime-only data in Suspense fallbacks. It also covers cacheTag and updateTag for targeted revalidation after mutations. The mental model is explicit: synchronous JSX and imports stay build-prerendered, cacheable fetches get TTL-controlled reuse, and user-specific or request-time data stays dynamic. Invoke while refactoring App Router pages before launch traffic spikes.

  • Enables cacheComponents in next.config.ts replacing experimental.ppr
  • Documents three content types: static prerender, cached use cache segments, and dynamic Suspense
  • Covers cacheLife profiles, cacheTag, and updateTag for invalidation
  • Shows mixing BlogPosts cached blocks with UserPreferences streamed inside Suspense

Next Cache Components by the numbers

  • 464 all-time installs (skills.sh)
  • +116 installs in the week ending Jul 27, 2026 (Skillselion tracking)
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vercel-labs/openreview --skill next-cache-components

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs464
repo stars1.5k
Security audit3 / 3 scanners passed
Last updatedMarch 6, 2026
Repositoryvercel-labs/openreview

What it does

Enable Next.js 16 Cache Components and Partial Prerendering so routes mix static shell, cached data, and dynamic Suspense in one page.

Who is it for?

Best when you're on Next.js 16+ and need marketing-speed shells with cached DB reads and fresh per-user corners on the same page.

Skip if: Legacy Next versions without Cache Components, or simple sites with no server data where static export is enough.

When should I use this skill?

Working with Next.js 16 Cache Components, PPR, use cache directive, cacheLife, cacheTag, or updateTag.

What you get

You enable PPR-style routes with static headers, cached data blocks, and Suspense-wrapped dynamic UI plus tag-based invalidation when data changes.

  • Route architecture with static, cached, and Suspense dynamic segments
  • cacheTag and updateTag invalidation plan for mutable data

By the numbers

  • Three content types: static auto-prerendered, cached use cache, and dynamic Suspense

Files

SKILL.mdMarkdownGitHub ↗

Cache Components (Next.js 16+)

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

Enable Cache Components

// next.config.ts
import 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:

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:

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:

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

'use cache'

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

Component Level

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

Function Level

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

---

Cache Profiles

Built-in Profiles

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

cacheLife() - Custom Lifetime

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

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

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:

'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:

'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

// Wrong - runtime API inside use cache
async function CachedProfile() {
  'use cache'
  const session = (await cookies()).get('session')?.value  // Error!
  return <div>{session}</div>
}

// Correct - extract outside, pass as argument
async 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:

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
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

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`):

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`):

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:

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:

Related skills

How it compares

Framework-native PPR and use cache patterns—not generic CDN cache headers or a one-off ISR cheat sheet from older Next docs.

FAQ

Who is next-cache-components for?

Developers shipping Next.js 16 App Router apps who want Partial Prerendering with explicit static, cached, and dynamic segments.

When should I use next-cache-components?

In Build when designing page architecture; in Ship perf when tuning cacheLife and tags; before Launch when OG-speed and freshness both matter on content routes.

Is next-cache-components safe to install?

It guides config and caching behavior—review the Security Audits panel on this page and validate cache invalidation does not expose private data in shared cached segments.

Frontend Developmentfrontenddevops

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.