SuperAPI AI Directory

cache-components

Frontend & websites

View repository

Updated 10-15 23:48

About this skill

Cache Components

Keep the project's caching mode unless migration is requested. Confirm the installed Next.js version and cacheComponents/partialPrefetching configuration before applying these patterns.

Read the relevant guide in node_modules/next/dist/docs/ first (bundled since 16.2; in a monorepo, use the app's own next package). Web paths map to files with numbered folders, so find them by name: .../functions/cacheLife is 01-app/03-api-reference/04-functions/cacheLife.md. Under 01-app/, start with 02-guides/migrating-to-cache-components.md, 01-getting-started/08-caching.md and 02-guides/instant-navigation.md. Error pages under /docs/messages are not bundled; next dev/next build print the fix options inline. Without local docs, use the official caching docs. Exact exports and signatures are also in node_modules/next/cache.d.ts. For static-output and staged-deferral questions in 16.4+, find the installed ensureStatic route-segment reference and navigation/prefetch function references by name.

The goal is a useful prerendered shell with freshness and authorization rules that remain correct after mutations, deploys and client navigation.

Facts older examples get wrong

  • Next.js 16 enables the mode with top-level cacheComponents: true. It replaces experimental.dynamicIO, experimental.useCache and experimental.ppr; cacheLife/cacheTag import from next/cache without unstable_. In 16.4, set partialPrefetching explicitly: leaving it unset warns; recommended create-next-app defaults enable both flags.
  • With it enabled, segment exports dynamic, revalidate, fetchCache and dynamicParams are errors, and Edge runtime is unsupported. revalidate = N becomes cacheLife (nearest or custom profile) inside use cache; force-dynamic and fetchCache are unnecessary; fetch next: { revalidate, tags } becomes cacheLife/cacheTag in a use cache function; request data goes under Suspense; dynamicParams = false becomes notFound() for unknown params.
  • Existing fetch and unstable_cache caching still works as a separate layer, so do not mechanically rewrite every read. Its persistence across deployments and instances depends on retained/shared storage; self-hosted instances do not share it by default. use cache is in-memory by default; even a durable handler cannot reuse entries when the build/deployment ID changes.
  • generateStaticParams must return at least one param: [] fails the build, and removing the export renders the route on every request.
  • revalidate: 0 or expire under 5 minutes makes a request-time hole instead of prerendered output; of the presets only seconds does. 16.3 adds stale thresholds (under 30 s: not prerendered; under 5 min: not in the App Shell).
  • Synchronous new Date(), Math.random() or crypto.randomUUID() during prerender is an error: capture it in use cache, or defer with await io() (next/cache, 16.3+) or await connection() (next/server) under Suspense.
  • use cache: private was experimental through 16.2. It runs at request time, stays out of the static shell and accepts no custom handler.
  • export const instant (16.3; unstable_instant in 16.2) opts a segment into instant-navigation validation. Since 16.3, every Page/Default segment is implicitly validated in next dev only (dev overlay) at the default level; build validation requires experimental.instantInsights.validationLevel set to experimental-error or experimental-manual-error (16.2 unstable_instant also validated at build). Test navigations with @next/playwright instant(). instant = false permits a blocking route; it does not clear synchronous-IO errors.

For whole-app adoption or instant-navigation work, Next.js publishes workflow skills (next-cache-components-adoption, next-cache-components-optimizer) in vercel/next.js/skills; the bundled migration guide covers the same steps.

Shape of one route

app/posts/page.tsx, checked against next 16.3.8 with cacheComponents: true (tsc --noEmit, next build, the action under next start). @/lib/* is project code.

import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag, updateTag } from 'next/cache'
import { insertPost, listPosts } from '@/lib/posts' // project code
import { requireEditor } from '@/lib/auth' // throws unless the session may edit

async function getPosts() {
  'use cache' // shared entry: no cookies/headers/searchParams inside
  cacheLife('hours')
  cacheTag('posts')
  return listPosts()
}

async function Theme() {
  const theme = (await cookies()).get('theme')?.value // request-time, outside the cache
  return <p>Theme: {theme ?? 'system'}</p>
}

async function addPost(formData: FormData) {
  'use server'
  await requireEditor()
  const title = String(formData.get('title') ?? '').trim()
  if (!title) return
  await insertPost(title)
  updateTag('posts') // Server Actions only; the next read waits for fresh data
}

export default async function Page() {
  const posts = await getPosts() // cached, so it can be part of the static shell
  return (
    <>
      <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
      <Suspense fallback={<p>Theme: …</p>}><Theme /></Suspense>
      <form action={addPost}><input name="title" /><button>Add</button></form>
    </>
  )
}

Decide the boundary

  • Cache reusable results when the product's freshness and data policy permit. Static content needs no directive just to be static.
  • A plain use cache scope cannot read cookies, headers, searchParams or call connection(), including indirectly through helpers. Read those outside and pass authorized, serializable values. Account/tenant IDs must participate in the cache key when output depends on them. On a dynamically rendered route, the next-request-in-use-cache error can pass next build and surface only under next start.
  • Keep uncached I/O and request-only content below appropriate Suspense boundaries. Suspense supplies fallback UI; it does not itself make synchronous work dynamic.
  • Evaluate private/remote variants against installed docs and deployment handlers. Private caching is not a compliance guarantee; remote caching is not a consistency protocol.

Define freshness and invalidation

Choose lifetimes from actual acceptable staleness. Named profiles can be overridden by the project; read its config before assuming a duration.

Use tags when invalidation must reach the same entity across routes, path invalidation for route-specific output, or expiry where that meets the contract. Authenticate, authorize and validate before mutations. Invalidate only affected cached data:

  • updateTag: immediate expiry/read-your-writes, Server Actions only.
  • revalidateTag(tag, 'max'): stale-while-revalidate on a later visit.
  • revalidateTag(tag, { expire: 0 }): immediate expiry where a webhook/Route Handler needs it. The one-argument form is deprecated.

These tag APIs can also apply to tagged fetch data; their availability does not by itself mean Cache Components is enabled.

Read for the problem

  • API boundaries: keys, serialization, lifetimes, handlers and route APIs.
  • Composition: tenant isolation, nested caches, pass-through and dynamic params.
  • Troubleshooting: diagnose blocking, stale or inconsistent output.

Run the production build, then exercise cold/direct navigation, client navigation, the relevant mutation and subsequent read under next start. Check tenant isolation and multi-instance invalidation when applicable. A passing build cannot establish those behaviors.