npx skills add https://github.com/vercel-labs/next-skills --skill next-cache-componentsHow Next Cache Components fits into a Paperclip company.
Next Cache Components drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
SKILL.md411 linesExpandCollapse
---name: next-cache-componentsdescription: Next.js 16 Cache Components - PPR, use cache directive, cacheLife, cacheTag, updateTag--- # 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: ```tsxexport 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: ```tsxasync 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: ```tsximport { 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 ```tsxexport async function CachedComponent() { 'use cache' const data = await fetchData() return <div>{data}</div>}``` ### Function Level ```tsxexport 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 ```tsximport { 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 ```tsxasync 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 ```tsximport { 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: ```tsxasync 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 ```tsxasync 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 ```tsximport { 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`):** ```tsximport { 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`):** ```tsximport { 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: ```tsximport { 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:- [Cache Components Guide](https://nextjs.org/docs/app/getting-started/cache-components)- [use cache Directive](https://nextjs.org/docs/app/api-reference/directives/use-cache)- [unstable_cache (legacy)](https://nextjs.org/docs/app/api-reference/functions/unstable_cache)Agent Browser
A comprehensive browser automation CLI that handles the full spectrum of web interaction tasks through Chrome DevTools Protocol. What impressed me most is the t
Agentcore
Install Agentcore skill for Claude Code from vercel-labs/agent-browser.
Core
Install Core skill for Claude Code from vercel-labs/json-render.