next-cache-components

Next.js Cache Components

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "next-cache-components" with this command: npx skills add oimiragieo/agent-studio/oimiragieo-agent-studio-next-cache-components

Next.js Cache Components

Deep expertise on the Next.js 16 caching model. Covers the 'use cache' directive, cacheLife() profiles, cacheTag() invalidation, cacheComponents configuration, and Partial Prerendering (PPR) integration.

When to Apply

Use this skill when:

  • Implementing caching in a Next.js 16+ application

  • Migrating from unstable_cache or revalidate patterns to the new caching API

  • Configuring component-level caching with cacheComponents

  • Setting up cache invalidation with tags

  • Integrating Partial Prerendering (PPR) with cached components

  • Choosing between static generation, ISR, and dynamic rendering

Core Concepts

The Caching Paradigm Shift (Next.js 15 to 16)

Next.js 16 introduces a fundamentally new caching model:

Feature Next.js 14 Next.js 15 Next.js 16

fetch() caching Cached by default Not cached by default Not cached by default

Route caching Automatic Opt-in 'use cache' directive

Data caching revalidate option revalidate option cacheLife() API

Invalidation revalidateTag()

revalidateTag()

cacheTag()

  • revalidateTag()

Component caching Not available Experimental cacheComponents: true

Key Principle

In Next.js 16, caching is explicit and opt-in. Nothing is cached unless you explicitly use the 'use cache' directive.

The 'use cache' Directive

Basic Usage

Add 'use cache' at the top of a file or function to enable caching:

// app/page.tsx -- cache the entire page 'use cache';

export default async function Page() { const data = await fetch('https://api.example.com/data'); const posts = await data.json();

return ( <main> {posts.map(post => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.body}</p> </article> ))} </main> ); }

Function-Level Caching

Cache individual async functions instead of entire pages:

// lib/data.ts import { cacheLife, cacheTag } from 'next/cache';

export async function getUser(id: string) { 'use cache'; cacheLife('hours'); cacheTag(user-${id});

const res = await fetch(https://api.example.com/users/${id}); return res.json(); }

export async function getPosts() { 'use cache'; cacheLife('minutes'); cacheTag('posts');

const res = await fetch('https://api.example.com/posts'); return res.json(); }

Component-Level Caching

With cacheComponents: true in next.config.ts , individual Server Components can be cached:

// next.config.ts const nextConfig = { cacheComponents: true, };

export default nextConfig;

// components/user-profile.tsx import { cacheLife, cacheTag } from 'next/cache';

export async function UserProfile({ userId }: { userId: string }) { 'use cache'; cacheLife('hours'); cacheTag(user-profile-${userId});

const user = await fetch(/api/users/${userId}).then(r => r.json());

return ( <div className="profile-card"> <img src={user.avatar} alt={user.name} /> <h2>{user.name}</h2> <p>{user.bio}</p> </div> ); }

Key benefit: The page can re-render while the cached component serves from cache, avoiding redundant data fetches for unchanged components.

Cache Profiles with cacheLife()

Built-in Profiles

import { cacheLife } from 'next/cache';

// Predefined profiles cacheLife('seconds'); // stale: 0, revalidate: 1s, expire: 60s cacheLife('minutes'); // stale: 5min, revalidate: 1min, expire: 1h cacheLife('hours'); // stale: 5min, revalidate: 1h, expire: 1d cacheLife('days'); // stale: 5min, revalidate: 1d, expire: 1w cacheLife('weeks'); // stale: 5min, revalidate: 1w, expire: 30d cacheLife('max'); // stale: 5min, revalidate: 30d, expire: 365d

Custom Profiles

Define custom cache profiles in next.config.ts :

// next.config.ts const nextConfig = { cacheLife: { 'blog-post': { stale: 300, // 5 minutes -- serve stale while revalidating revalidate: 3600, // 1 hour -- revalidate in background expire: 86400, // 1 day -- maximum cache lifetime }, 'user-session': { stale: 0, // Never serve stale revalidate: 60, // Revalidate every minute expire: 300, // Expire after 5 minutes }, 'static-content': { stale: 3600, // 1 hour stale tolerance revalidate: 86400, // Revalidate daily expire: 604800, // Expire after 1 week }, }, };

Usage:

async function getBlogPost(slug: string) { 'use cache'; cacheLife('blog-post'); cacheTag(blog-${slug});

return fetch(/api/posts/${slug}).then(r => r.json()); }

Profile Selection Guide

Content Type Profile Rationale

Static pages 'max' or 'weeks'

Content rarely changes

Blog posts 'days' or custom Updated occasionally

Product listings 'hours'

Prices/stock change moderately

User dashboards 'minutes'

Data updates frequently

Real-time feeds 'seconds' or no cache Data changes constantly

Auth-dependent custom (stale: 0) Must never serve stale auth data

Cache Invalidation with cacheTag()

Tagging Cached Data

import { cacheTag } from 'next/cache';

async function getProduct(id: string) { 'use cache'; cacheLife('hours'); cacheTag('products', product-${id});

return fetch(/api/products/${id}).then(r => r.json()); }

Invalidating Cache

Use revalidateTag() in Server Actions or Route Handlers:

// app/actions.ts 'use server';

import { revalidateTag } from 'next/cache';

export async function updateProduct(id: string, data: ProductData) { await db.products.update(id, data);

// Invalidate specific product cache revalidateTag(product-${id});

// Invalidate all products listing revalidateTag('products'); }

Tag Naming Conventions

entity-type -> 'products', 'users', 'posts' entity-type-id -> 'product-123', 'user-456' entity-type-relation -> 'product-reviews', 'user-orders' entity-type-relation-id -> 'product-123-reviews'

Hierarchical Invalidation

// Tag hierarchy for a blog cacheTag('blog'); // All blog content cacheTag('blog', blog-${slug}); // Specific post cacheTag('blog', 'blog-comments'); // All comments cacheTag('blog', blog-comments-${postId}); // Post comments

// Invalidate all blog content revalidateTag('blog');

// Invalidate just one post revalidateTag(blog-${slug});

Partial Prerendering (PPR) Integration

PPR combines static shells with dynamic holes, and 'use cache' works with it.

How PPR + Cache Works

// app/product/[id]/page.tsx import { Suspense } from 'react'; import { ProductDetails } from './product-details'; import { RecommendedProducts } from './recommended'; import { UserReviews } from './reviews';

// Static shell (prerendered at build time) export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params;

return ( <main> {/* Cached component -- serves from cache */} <ProductDetails productId={id} />

  {/* Dynamic holes -- rendered on request */}
  &#x3C;Suspense fallback={&#x3C;ReviewsSkeleton />}>
    &#x3C;UserReviews productId={id} />
  &#x3C;/Suspense>

  &#x3C;Suspense fallback={&#x3C;RecommendedSkeleton />}>
    &#x3C;RecommendedProducts productId={id} />
  &#x3C;/Suspense>
&#x3C;/main>

); }

// components/product-details.tsx import { cacheLife, cacheTag } from 'next/cache';

export async function ProductDetails({ productId }: { productId: string }) { 'use cache'; cacheLife('hours'); cacheTag(product-${productId});

const product = await fetch(/api/products/${productId}).then(r => r.json());

return ( <section> <h1>{product.name}</h1> <p>{product.description}</p> <span>${product.price}</span> </section> ); }

Enable PPR

// next.config.ts const nextConfig = { ppr: true, // Enable Partial Prerendering cacheComponents: true, // Enable component-level caching };

Migration from Previous Caching APIs

From unstable_cache (Next.js 14/15)

// Before (Next.js 14/15) import { unstable_cache } from 'next/cache';

const getCachedUser = unstable_cache( async (id: string) => { return db.users.findUnique({ where: { id } }); }, ['user'], { revalidate: 3600, tags: ['users'] } );

// After (Next.js 16) import { cacheLife, cacheTag } from 'next/cache';

async function getUser(id: string) { 'use cache'; cacheLife('hours'); cacheTag('users', user-${id});

return db.users.findUnique({ where: { id } }); }

From fetch revalidate Option

// Before (Next.js 14) const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600, tags: ['data'] }, });

// After (Next.js 16) async function getData() { 'use cache'; cacheLife('hours'); cacheTag('data');

return fetch('https://api.example.com/data').then(r => r.json()); }

From generateStaticParams + revalidate

// Before (Next.js 14/15) export const revalidate = 3600;

export async function generateStaticParams() { const posts = await getPosts(); return posts.map(post => ({ slug: post.slug })); }

// After (Next.js 16) -- use 'use cache' at page level ('use cache');

import { cacheLife } from 'next/cache';

cacheLife('hours');

export default async function Page({ params }) { const { slug } = await params; // ... }

Common Patterns

Cached Data Layer

Create a centralized data access layer with caching:

// lib/data/products.ts import { cacheLife, cacheTag } from 'next/cache';

export async function getProduct(id: string) { 'use cache'; cacheLife('hours'); cacheTag('products', product-${id});

return prisma.product.findUnique({ where: { id } }); }

export async function getProducts(category?: string) { 'use cache'; cacheLife('minutes'); cacheTag('products', category ? category-${category} : 'all-products');

return prisma.product.findMany({ where: category ? { category } : undefined, orderBy: { createdAt: 'desc' }, }); }

Auth-Aware Caching

Cache public data but keep auth-dependent data dynamic:

// Cached: product data (same for all users) async function ProductInfo({ id }: { id: string }) { 'use cache'; cacheLife('hours'); cacheTag(product-${id});

const product = await getProduct(id); return <ProductCard product={product} />; }

// NOT cached: user-specific data async function UserCartStatus({ userId }: { userId: string }) { // No 'use cache' -- always dynamic const cart = await getCart(userId); return <CartBadge count={cart.items.length} />; }

Iron Laws

  • ALWAYS use 'use cache' explicitly on every component or function you intend to cache — in Next.js 16, nothing is cached unless you opt in; implicit caching assumptions from Next.js 14 are gone.

  • NEVER use 'use cache' on components that render user-specific or auth-dependent data — the cache key does not include session context; different users will receive each other's cached content.

  • ALWAYS call cacheTag() on every cached function that reads mutable data — without tags, there is no way to invalidate stale data after a mutation; the only recourse is waiting for expiry.

  • NEVER cache Server Actions that perform mutations — 'use cache' returns a cached response instead of executing the mutation; data changes are silently discarded.

  • ALWAYS call revalidateTag() in Server Actions or Route Handlers immediately after a mutation — forgetting invalidation means stale data persists for the full cache lifetime after every write.

Anti-Patterns

Anti-Pattern Why It Fails Correct Approach

Using 'use cache' on auth-dependent components Cache key excludes session context; different users receive each other's cached data Keep auth-dependent components dynamic; cache only public, user-agnostic data

Caching Server Actions that mutate data Returns cached response instead of executing mutation; writes are silently discarded Never put 'use cache' on mutation actions; only cache read operations

Missing cacheTag() on mutable data No invalidation path; stale data persists until expiry with no way to purge on mutation Always tag cached data: cacheTag('entity', 'entity-id')

Forgetting revalidateTag() after mutations Stale data persists for full cache lifetime after every write Call revalidateTag() in every Server Action or Route Handler that modifies data

Overly broad cache tag names revalidateTag('all') invalidates the entire cache on every mutation — defeats purpose Use granular hierarchical tags: 'products' , 'product-{id}'

References

  • Next.js Caching Documentation

  • use cache Directive

  • cacheLife API

  • cacheTag API

  • Partial Prerendering

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

filesystem

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

slack-notifications

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

chrome-browser

No summary provided by upstream source.

Repository SourceNeeds Review