
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-componentsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 464 |
|---|---|
| repo stars | ★ 1.5k |
| Security audit | 3 / 3 scanners passed |
| Last updated | March 6, 2026 |
| Repository | vercel-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
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 nextConfigThis 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 APIscacheLife() - 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 Config | Replacement |
|---|---|
experimental.ppr | cacheComponents: true |
dynamic = 'force-dynamic' | Remove (default behavior) |
dynamic = 'force-static' | 'use cache' + cacheLife('max') |
revalidate = N | cacheLife({ 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 cachegenerates keys automatically from function arguments and closures. ThekeyPartsarray fromunstable_cacheis no longer needed. - Tags - Replace
options.tagswithcacheTag()calls inside the function. - Revalidation - Replace
options.revalidatewithcacheLife({ revalidate: N })or a built-in profile likecacheLife('minutes'). - Dynamic data -
unstable_cachedid not supportcookies()orheaders()inside the callback. The same restriction applies touse 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 insideuse 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.