home / skills / first-fluke / fullstack-starter / cache-components

cache-components skill

/.agents/skills/cache-components

This skill guides you to implement Next.js Cache Components and Partial Prerendering, optimizing caching decisions and data fetching for server components.

npx playbooks add skill first-fluke/fullstack-starter --skill cache-components

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.7 KB
---
name: cache-components
description: Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). PROACTIVE ACTIVATION when cacheComponents config is detected.
---

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR).

**PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their `next.config.ts` or `next.config.js`.

**DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in `next.config`. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions.

**USE CASES**:
- Implementing `'use cache'` directive
- Configuring cache lifetimes with `cacheLife()`
- Tagging cached data with `cacheTag()`
- Invalidating caches with `updateTag()` / `revalidateTag()`
- Optimizing static vs dynamic content boundaries
- Debugging cache issues
- Reviewing Cache Component implementations

## Project Detection

When starting work in a Next.js project, check if Cache Components are enabled:

```bash
# Check next.config.ts or next.config.js for cacheComponents
grep -r "cacheComponents" next.config.* 2>/dev/null
```

If `cacheComponents: true` is found, apply this skill's patterns proactively when:
- Writing React Server Components
- Implementing data fetching
- Creating Server Actions with mutations
- Optimizing page performance
- Reviewing existing component code

## Core Concept: The Caching Decision Tree

When writing a **React Server Component**, ask:

1. **Does it depend on request context?** (cookies, headers, searchParams)
2. **Can this be cached?** (Is the output the same for all users?)
   - **YES** -> `'use cache'` + `cacheTag()` + `cacheLife()`
   - **NO** -> Wrap in `<Suspense>` (dynamic streaming)

## Philosophy: Code Over Configuration

Cache Components represents a shift from segment-based configuration to compositional code:

- **Before (Deprecated)**: `export const revalidate = 3600`
- **After**: `cacheLife('hours')` inside `'use cache'`

- **Before (Deprecated)**: `export const dynamic = 'force-static'`
- **After**: Use `'use cache'` and Suspense boundaries

## Quick Start

### 1. Enable Configuration
```typescript
// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    ppr: true,
    dynamicIO: true, // often correlated features
  },
  // Ensure basic cache components flag if required by your version
};

export default nextConfig;
```

### 2. Basic Usage

```typescript
import { cacheLife } from 'next/cache';

async function CachedPosts() {
  'use cache'
  cacheLife('hours'); // Cache for hours

  const posts = await db.posts.findMany();
  return <PostList posts={posts} />;
}
```

## Core APIs

### `'use cache'`
Marks a function, component, or file as cacheable. The return value is cached and shared across requests.

### `cacheLife(profile)`
Control cache duration using semantic profiles:
- `'seconds'`: Short-lived
- `'minutes'`: Medium-lived
- `'hours'`: Long-lived
- `'days'`: Very long-lived
- `'weeks'`: Static-like content
- `'max'`: Permanent cache

### `cacheTag(...tags)`
Tag cached data for on-demand invalidation.

```typescript
import { cacheTag } from 'next/cache';

async function getUserProfile(id: string) {
  'use cache'
  cacheTag('user-profile', `user-${id}`);
  // ... fetch logic
}
```

### `revalidateTag(tag)` / `expireTag(tag)`
Invalidate cached data in background or immediately.

```typescript
'use server'
import { expireTag } from 'next/cache';

export async function updateUser(id: string, data: any) {
  await db.user.update({ where: { id }, data });
  expireTag(`user-${id}`); // Invalidate specific cache
}
```

Overview

This skill provides expert guidance for using Next.js Cache Components and Partial Prerendering (PPR). It activates proactively when a project’s next.config contains cacheComponents: true and guides component authoring, data fetching, tagging, and invalidation patterns. The goal is predictable caching behavior and clear guidance for static vs dynamic boundaries.

How this skill works

At session start it detects cacheComponents: true in next.config and applies a decision tree for React Server Components: determine request-context dependency, then choose caching or dynamic streaming. It recommends using the 'use cache' directive with cacheLife() and cacheTag() for cacheable outputs, and Suspense/dynamic streaming for request-specific content. It also covers server actions that mutate data and how to invalidate caches with expireTag() / revalidateTag().

When to use it

  • Working in a Next.js repo with cacheComponents: true in next.config
  • Authoring React Server Components that may be shared across requests
  • Implementing server-side data fetching and deciding cache lifetimes
  • Creating Server Actions that update data and must trigger cache invalidation
  • Optimizing page performance by moving static work into cacheable components

Best practices

  • Always ask: does the component depend on request context (cookies, headers, searchParams)? If yes, avoid 'use cache'.
  • Prefer 'use cache' + cacheTag() + cacheLife() for outputs identical across users.
  • Use semantic cacheLife profiles ('seconds','minutes','hours','days','weeks','max') instead of numeric revalidate exports.
  • Tag cacheable artifacts with meaningful tags (resource type + id) to enable precise invalidation.
  • On mutations, call expireTag()/revalidateTag() in server actions to update cache in background or immediately.
  • Wrap user-specific or per-request parts in Suspense boundaries so the rest remains cacheable.

Example use cases

  • Caching a public PostList component: 'use cache', cacheLife('hours'), cacheTag('posts')
  • Caching user profiles by id: cacheTag('user-profile', `user-${id}`) and expireTag on updates
  • Building a product catalog with weeks or max cacheLife for static assets and hours for inventory counts
  • Implementing a Server Action to update a record and calling expireTag to refresh affected caches
  • Debugging stale UI by checking tags and verifying expireTag/revalidateTag calls in mutation flows

FAQ

How do I decide between cacheLife profiles?

Choose based on expected content volatility: seconds for fast-changing data, hours for often-read but occasionally-updated content, weeks/max for static assets.

What if a component sometimes needs request context?

Split it: keep the cacheable portion with 'use cache' and render the request-specific part inside a Suspense or client component.

When should I use tags vs global invalidation?

Prefer tags for targeted invalidation (by resource id/type). Global invalidation is only for sweeping changes like schema migrations.