{
  "schemaVersion": "1.0",
  "item": {
    "slug": "next-cache-components",
    "name": "Next Cache Components",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/TuanViDev/next-cache-components",
    "canonicalUrl": "https://clawhub.ai/TuanViDev/next-cache-components",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/next-cache-components",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=next-cache-components",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/next-cache-components"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/next-cache-components",
    "agentPageUrl": "https://openagent3.xyz/skills/next-cache-components/agent",
    "manifestUrl": "https://openagent3.xyz/skills/next-cache-components/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/next-cache-components/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Cache Components (Next.js 16+)",
        "body": "Cache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route."
      },
      {
        "title": "Enable Cache Components",
        "body": "// next.config.ts\nimport type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n  cacheComponents: true,\n}\n\nexport default nextConfig\n\nThis replaces the old experimental.ppr flag."
      },
      {
        "title": "Three Content Types",
        "body": "With Cache Components enabled, content falls into three categories:"
      },
      {
        "title": "1. Static (Auto-Prerendered)",
        "body": "Synchronous code, imports, pure computations - prerendered at build time:\n\nexport default function Page() {\n  return (\n    <header>\n      <h1>Our Blog</h1>  {/* Static - instant */}\n      <nav>...</nav>\n    </header>\n  )\n}"
      },
      {
        "title": "2. Cached (use cache)",
        "body": "Async data that doesn't need fresh fetches every request:\n\nasync function BlogPosts() {\n  'use cache'\n  cacheLife('hours')\n\n  const posts = await db.posts.findMany()\n  return <PostList posts={posts} />\n}"
      },
      {
        "title": "3. Dynamic (Suspense)",
        "body": "Runtime data that must be fresh - wrap in Suspense:\n\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}"
      },
      {
        "title": "File Level",
        "body": "'use cache'\n\nexport default async function Page() {\n  // Entire page is cached\n  const data = await fetchData()\n  return <div>{data}</div>\n}"
      },
      {
        "title": "Component Level",
        "body": "export async function CachedComponent() {\n  'use cache'\n  const data = await fetchData()\n  return <div>{data}</div>\n}"
      },
      {
        "title": "Function Level",
        "body": "export async function getData() {\n  'use cache'\n  return db.query('SELECT * FROM posts')\n}"
      },
      {
        "title": "Built-in Profiles",
        "body": "'use cache'                    // Default: 5m stale, 15m revalidate\n\n'use cache: remote'           // Platform-provided cache (Redis, KV)\n\n'use cache: private'          // For compliance, allows runtime APIs"
      },
      {
        "title": "cacheLife() - Custom Lifetime",
        "body": "import { cacheLife } from 'next/cache'\n\nasync function getData() {\n  'use cache'\n  cacheLife('hours')  // Built-in profile\n  return fetch('/api/data')\n}\n\nBuilt-in profiles: 'default', 'minutes', 'hours', 'days', 'weeks', 'max'"
      },
      {
        "title": "Inline Configuration",
        "body": "async 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}"
      },
      {
        "title": "cacheTag() - Tag Cached Content",
        "body": "import { 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}"
      },
      {
        "title": "updateTag() - Immediate Invalidation",
        "body": "Use when you need the cache refreshed within the same request:\n\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}"
      },
      {
        "title": "revalidateTag() - Background Revalidation",
        "body": "Use for stale-while-revalidate behavior:\n\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}"
      },
      {
        "title": "Runtime Data Constraint",
        "body": "Cannot access cookies(), headers(), or searchParams inside use cache."
      },
      {
        "title": "Solution: Pass as Arguments",
        "body": "// 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}"
      },
      {
        "title": "Exception: use cache: private",
        "body": "For compliance requirements when you can't refactor:\n\nasync function getData() {\n  'use cache: private'\n  const session = (await cookies()).get('session')?.value  // Allowed\n  return fetchData(session)\n}"
      },
      {
        "title": "Cache Key Generation",
        "body": "Cache keys are automatic based on:\n\nBuild ID - invalidates all caches on deploy\nFunction ID - hash of function location\nSerializable arguments - props become part of key\nClosure variables - outer scope values included\n\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}"
      },
      {
        "title": "Complete Example",
        "body": "import { 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}"
      },
      {
        "title": "Migration from Previous Versions",
        "body": "Old ConfigReplacementexperimental.pprcacheComponents: truedynamic = 'force-dynamic'Remove (default behavior)dynamic = 'force-static''use cache' + cacheLife('max')revalidate = NcacheLife({ revalidate: N })unstable_cache()'use cache' directive"
      },
      {
        "title": "Migrating unstable_cache to use cache",
        "body": "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\nBefore (unstable_cache):\n\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\nAfter (use cache):\n\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\nKey differences:\n\nNo manual cache keys - use cache generates keys automatically from function arguments and closures. The keyParts array from unstable_cache is no longer needed.\nTags - Replace options.tags with cacheTag() calls inside the function.\nRevalidation - Replace options.revalidate with cacheLife({ revalidate: N }) or a built-in profile like cacheLife('minutes').\nDynamic 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."
      },
      {
        "title": "Limitations",
        "body": "Edge runtime not supported - requires Node.js\nStatic export not supported - needs server\nNon-deterministic values (Math.random(), Date.now()) execute once at build time inside use cache\n\nFor request-time randomness outside cache:\n\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\nSources:\n\nCache Components Guide\nuse cache Directive\nunstable_cache (legacy)"
      }
    ],
    "body": "Cache Components (Next.js 16+)\n\nCache Components enable Partial Prerendering (PPR) - mix static, cached, and dynamic content in a single route.\n\nEnable Cache Components\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\nThree Content Types\n\nWith Cache Components enabled, content falls into three categories:\n\n1. Static (Auto-Prerendered)\n\nSynchronous code, imports, pure computations - prerendered at build time:\n\nexport default function Page() {\n  return (\n    <header>\n      <h1>Our Blog</h1>  {/* Static - instant */}\n      <nav>...</nav>\n    </header>\n  )\n}\n\n2. Cached (use cache)\n\nAsync data that doesn't need fresh fetches every request:\n\nasync function BlogPosts() {\n  'use cache'\n  cacheLife('hours')\n\n  const posts = await db.posts.findMany()\n  return <PostList posts={posts} />\n}\n\n3. Dynamic (Suspense)\n\nRuntime data that must be fresh - wrap in Suspense:\n\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\nuse cache Directive\nFile Level\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\nComponent Level\nexport async function CachedComponent() {\n  'use cache'\n  const data = await fetchData()\n  return <div>{data}</div>\n}\n\nFunction Level\nexport async function getData() {\n  'use cache'\n  return db.query('SELECT * FROM posts')\n}\n\nCache Profiles\nBuilt-in Profiles\n'use cache'                    // Default: 5m stale, 15m revalidate\n\n'use cache: remote'           // Platform-provided cache (Redis, KV)\n\n'use cache: private'          // For compliance, allows runtime APIs\n\ncacheLife() - Custom Lifetime\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\nInline Configuration\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\nCache Invalidation\ncacheTag() - Tag Cached Content\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\nupdateTag() - Immediate Invalidation\n\nUse when you need the cache refreshed within the same request:\n\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\nrevalidateTag() - Background Revalidation\n\nUse for stale-while-revalidate behavior:\n\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\nRuntime Data Constraint\n\nCannot access cookies(), headers(), or searchParams inside use cache.\n\nSolution: Pass as Arguments\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\nException: use cache: private\n\nFor compliance requirements when you can't refactor:\n\nasync function getData() {\n  'use cache: private'\n  const session = (await cookies()).get('session')?.value  // Allowed\n  return fetchData(session)\n}\n\nCache Key Generation\n\nCache keys are automatic based on:\n\nBuild ID - invalidates all caches on deploy\nFunction ID - hash of function location\nSerializable arguments - props become part of key\nClosure variables - outer scope values included\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\nComplete Example\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\nMigration from Previous Versions\nOld Config\tReplacement\nexperimental.ppr\tcacheComponents: true\ndynamic = 'force-dynamic'\tRemove (default behavior)\ndynamic = 'force-static'\t'use cache' + cacheLife('max')\nrevalidate = N\tcacheLife({ revalidate: N })\nunstable_cache()\t'use cache' directive\nMigrating unstable_cache to use cache\n\nunstable_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\nBefore (unstable_cache):\n\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\nAfter (use cache):\n\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\nNo manual cache keys - use cache generates keys automatically from function arguments and closures. The keyParts array from unstable_cache is no longer needed.\nTags - Replace options.tags with cacheTag() calls inside the function.\nRevalidation - Replace options.revalidate with cacheLife({ revalidate: N }) or a built-in profile like cacheLife('minutes').\nDynamic 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.\nLimitations\nEdge runtime not supported - requires Node.js\nStatic export not supported - needs server\nNon-deterministic values (Math.random(), Date.now()) execute once at build time inside use cache\n\nFor request-time randomness outside cache:\n\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\nCache Components Guide\nuse cache Directive\nunstable_cache (legacy)"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/TuanViDev/next-cache-components",
    "publisherUrl": "https://clawhub.ai/TuanViDev/next-cache-components",
    "owner": "TuanViDev",
    "version": "0.1.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/next-cache-components",
    "downloadUrl": "https://openagent3.xyz/downloads/next-cache-components",
    "agentUrl": "https://openagent3.xyz/skills/next-cache-components/agent",
    "manifestUrl": "https://openagent3.xyz/skills/next-cache-components/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/next-cache-components/agent.md"
  }
}