← VercelCONTENT HISTORY

Update to Vercel

Snapshot Sep 30, 2026 · 23:18 UTC · version 0.21.4

Collection source: not recorded for this historical snapshot. These snapshots do not have a confirmed matching collection source. Differences in file lists alone do not establish changes to the package.

WHAT CHANGED · RULE-BASED ANALYSIS

Supporting file metadata differs

Newly listed paths: agents/openai.yaml. This compares saved file lists, not package contents; a different collection source can change the list.

Observed in package metadata. These changes alone do not establish a new customer-facing feature.

Supporting files

Before

[]

After

[{"relative_path":"agents/openai.yaml","size_in_bytes":129}]

Compare saved observations

Download comparison JSON
Full technical diff · 1 changed fields

changed /included_files

BEFORE
[]
AFTER
[
  {
    "relative_path": "agents/openai.yaml",
    "size_in_bytes": 129
  }
]
Full snapshot data
{
  "name": "next-cache-components",
  "description": "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.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 129
    }
  ],
  "skill_md_contents": "---\nname: next-cache-components\ndescription: 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.\nmetadata:\n  priority: 6\n  docs:\n    - \"https://nextjs.org/docs/app/getting-started/cache-components\"\n    - \"https://nextjs.org/docs/app/api-reference/directives/use-cache\"\n  pathPatterns:\n    - 'next.config.*'\n    - 'app/**'\n    - 'src/app/**'\n    - 'apps/*/app/**'\n    - 'apps/*/src/app/**'\n  importPatterns:\n    - \"next/cache\"\n  bashPatterns:\n    - '\\bnext\\s+(dev|build)\\b'\n  promptSignals:\n    phrases:\n      - \"use cache\"\n      - \"cache components\"\n      - \"partial prerendering\"\n      - \"PPR\"\n      - \"cacheLife\"\n      - \"cacheTag\"\n      - \"updateTag\"\n      - \"unstable_cache\"\n    allOf:\n      - [cache, component]\n      - [cache, directive]\n      - [partial, prerender]\n    anyOf:\n      - \"revalidateTag\"\n      - \"stale\"\n      - \"revalidate\"\n      - \"cache profile\"\n    noneOf: []\n    minScore: 6\n---\n\n# Cache Components (Next.js 16+)\n\nCache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route.\n\n## Enable Cache Components\n\n```ts\n// next.config.ts\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n  cacheComponents: true,\n}\n\nexport default nextConfig\n```\n\nThis replaces the old `experimental.ppr` flag.\n\n---\n\n## Three Content Types\n\nWith Cache Components enabled, content falls into three categories:\n\n### 1. Static (Auto-Prerendered)\n\nSynchronous code, imports, pure computations - prerendered at build time:\n\n```tsx\nexport default function Page() {\n  return (\n    <header>\n      <h1>Our Blog</h1>  {/* Static - instant */}\n      <nav>...</nav>\n    </header>\n  )\n}\n```\n\n### 2. Cached (`use cache`)\n\nAsync data that doesn't need fresh fetches every request:\n\n```tsx\nasync function BlogPosts() {\n  'use cache'\n  cacheLife('hours')\n\n  const posts = await db.posts.findMany()\n  return <PostList posts={posts} />\n}\n```\n\n### 3. Dynamic (Suspense)\n\nRuntime data that must be fresh - wrap in Suspense:\n\n```tsx\nimport { Suspense } from 'react'\n\nexport default function Page() {\n  return (\n    <>\n      <BlogPosts />  {/* Cached */}\n\n      <Suspense fallback={<p>Loading...</p>}>\n        <UserPreferences />  {/* Dynamic - streams in */}\n      </Suspense>\n    </>\n  )\n}\n\nasync function UserPreferences() {\n  const theme = (await cookies()).get('theme')?.value\n  return <p>Theme: {theme}</p>\n}\n```\n\n---\n\n## `use cache` Directive\n\n### File Level\n\n```tsx\n'use cache'\n\nexport default async function Page() {\n  // Entire page is cached\n  const data = await fetchData()\n  return <div>{data}</div>\n}\n```\n\n### Component Level\n\n```tsx\nexport async function CachedComponent() {\n  'use cache'\n  const data = await fetchData()\n  return <div>{data}</div>\n}\n```\n\n### Function Level\n\n```tsx\nexport async function getData() {\n  'use cache'\n  return db.query('SELECT * FROM posts')\n}\n```\n\n---\n\n## Cache Profiles\n\n### Built-in Profiles\n\n```tsx\n'use cache'                    // Default: 5m stale, 15m revalidate\n```\n\n```tsx\n'use cache: remote'           // Platform-provided cache (Redis, KV)\n```\n\n```tsx\n'use cache: private'          // For compliance, allows runtime APIs\n```\n\n### `cacheLife()` - Custom Lifetime\n\n```tsx\nimport { cacheLife } from 'next/cache'\n\nasync function getData() {\n  'use cache'\n  cacheLife('hours')  // Built-in profile\n  return fetch('/api/data')\n}\n```\n\nBuilt-in profiles: `'default'`, `'minutes'`, `'hours'`, `'days'`, `'weeks'`, `'max'`\n\n### Inline Configuration\n\n```tsx\nasync function getData() {\n  'use cache'\n  cacheLife({\n    stale: 3600,      // 1 hour - serve stale while revalidating\n    revalidate: 7200, // 2 hours - background revalidation interval\n    expire: 86400,    // 1 day - hard expiration\n  })\n  return fetch('/api/data')\n}\n```\n\n---\n\n## Cache Invalidation\n\n### `cacheTag()` - Tag Cached Content\n\n```tsx\nimport { cacheTag } from 'next/cache'\n\nasync function getProducts() {\n  'use cache'\n  cacheTag('products')\n  return db.products.findMany()\n}\n\nasync function getProduct(id: string) {\n  'use cache'\n  cacheTag('products', `product-${id}`)\n  return db.products.findUnique({ where: { id } })\n}\n```\n\n### `updateTag()` - Immediate Invalidation\n\nUse when you need the cache refreshed within the same request:\n\n```tsx\n'use server'\n\nimport { updateTag } from 'next/cache'\n\nexport async function updateProduct(id: string, data: FormData) {\n  await db.products.update({ where: { id }, data })\n  updateTag(`product-${id}`)  // Immediate - same request sees fresh data\n}\n```\n\n### `revalidateTag()` - Background Revalidation\n\nUse for stale-while-revalidate behavior:\n\n```tsx\n'use server'\n\nimport { revalidateTag } from 'next/cache'\n\nexport async function createPost(data: FormData) {\n  await db.posts.create({ data })\n  revalidateTag('posts')  // Background - next request sees fresh data\n}\n```\n\n---\n\n## Runtime Data Constraint\n\n**Cannot** access `cookies()`, `headers()`, or `searchParams` inside `use cache`.\n\n### Solution: Pass as Arguments\n\n```tsx\n// Wrong - runtime API inside use cache\nasync function CachedProfile() {\n  'use cache'\n  const session = (await cookies()).get('session')?.value  // Error!\n  return <div>{session}</div>\n}\n\n// Correct - extract outside, pass as argument\nasync function ProfilePage() {\n  const session = (await cookies()).get('session')?.value\n  return <CachedProfile sessionId={session} />\n}\n\nasync function CachedProfile({ sessionId }: { sessionId: string }) {\n  'use cache'\n  // sessionId becomes part of cache key automatically\n  const data = await fetchUserData(sessionId)\n  return <div>{data.name}</div>\n}\n```\n\n### Exception: `use cache: private`\n\nFor compliance requirements when you can't refactor:\n\n```tsx\nasync function getData() {\n  'use cache: private'\n  const session = (await cookies()).get('session')?.value  // Allowed\n  return fetchData(session)\n}\n```\n\n---\n\n## Cache Key Generation\n\nCache keys are automatic based on:\n- **Build ID** - invalidates all caches on deploy\n- **Function ID** - hash of function location\n- **Serializable arguments** - props become part of key\n- **Closure variables** - outer scope values included\n\n```tsx\nasync function Component({ userId }: { userId: string }) {\n  const getData = async (filter: string) => {\n    'use cache'\n    // Cache key = userId (closure) + filter (argument)\n    return fetch(`/api/users/${userId}?filter=${filter}`)\n  }\n  return getData('active')\n}\n```\n\n---\n\n## Complete Example\n\n```tsx\nimport { Suspense } from 'react'\nimport { cookies } from 'next/headers'\nimport { cacheLife, cacheTag } from 'next/cache'\n\nexport default function DashboardPage() {\n  return (\n    <>\n      {/* Static shell - instant from CDN */}\n      <header><h1>Dashboard</h1></header>\n      <nav>...</nav>\n\n      {/* Cached - fast, revalidates hourly */}\n      <Stats />\n\n      {/* Dynamic - streams in with fresh data */}\n      <Suspense fallback={<NotificationsSkeleton />}>\n        <Notifications />\n      </Suspense>\n    </>\n  )\n}\n\nasync function Stats() {\n  'use cache'\n  cacheLife('hours')\n  cacheTag('dashboard-stats')\n\n  const stats = await db.stats.aggregate()\n  return <StatsDisplay stats={stats} />\n}\n\nasync function Notifications() {\n  const userId = (await cookies()).get('userId')?.value\n  const notifications = await db.notifications.findMany({\n    where: { userId, read: false }\n  })\n  return <NotificationList items={notifications} />\n}\n```\n\n---\n\n## Migration from Previous Versions\n\n| Old Config | Replacement |\n|-----------|-------------|\n| `experimental.ppr` | `cacheComponents: true` |\n| `dynamic = 'force-dynamic'` | Remove (default behavior) |\n| `dynamic = 'force-static'` | `'use cache'` + `cacheLife('max')` |\n| `revalidate = N` | `cacheLife({ revalidate: N })` |\n| `unstable_cache()` | `'use cache'` directive |\n\n### Migrating `unstable_cache` to `use cache`\n\n`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:\n\n**Before (`unstable_cache`):**\n\n```tsx\nimport { unstable_cache } from 'next/cache'\n\nconst getCachedUser = unstable_cache(\n  async (id) => getUser(id),\n  ['my-app-user'],\n  {\n    tags: ['users'],\n    revalidate: 60,\n  }\n)\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const user = await getCachedUser(id)\n  return <div>{user.name}</div>\n}\n```\n\n**After (`use cache`):**\n\n```tsx\nimport { cacheLife, cacheTag } from 'next/cache'\n\nasync function getCachedUser(id: string) {\n  'use cache'\n  cacheTag('users')\n  cacheLife({ revalidate: 60 })\n  return getUser(id)\n}\n\nexport default async function Page({ params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params\n  const user = await getCachedUser(id)\n  return <div>{user.name}</div>\n}\n```\n\nKey differences:\n- **No manual cache keys** - `use cache` generates keys automatically from function arguments and closures. The `keyParts` array from `unstable_cache` is no longer needed.\n- **Tags** - Replace `options.tags` with `cacheTag()` calls inside the function.\n- **Revalidation** - Replace `options.revalidate` with `cacheLife({ revalidate: N })` or a built-in profile like `cacheLife('minutes')`.\n- **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.\n\n---\n\n## Limitations\n\n- **Edge runtime not supported** - requires Node.js\n- **Static export not supported** - needs server\n- **Non-deterministic values** (`Math.random()`, `Date.now()`) execute once at build time inside `use cache`\n\nFor request-time randomness outside cache:\n\n```tsx\nimport { connection } from 'next/server'\n\nasync function DynamicContent() {\n  await connection()  // Defer to request time\n  const id = crypto.randomUUID()  // Different per request\n  return <div>{id}</div>\n}\n```\n\nSources:\n- [Cache Components Guide](https://nextjs.org/docs/app/getting-started/cache-components)\n- [use cache Directive](https://nextjs.org/docs/app/api-reference/directives/use-cache)\n- [unstable_cache (legacy)](https://nextjs.org/docs/app/api-reference/functions/unstable_cache)\n"
}

SHA-256: 630cd16c863517ef64d87d3915f6c85737b72fbc81211b23f14ec6d4f1664476