mcpField
C
cache-components

cache-components

github.com/zoonk開発15839 回インストール3 週間前に更新
リポジトリ

cache-components について

cache-componentsは、Cache Componentsを有効にしたNext.jsプロジェクト向けの開発スキルです。キャッシュAPIとSuspense境界を使い、静的・キャッシュ済み・動的なコンテンツを組み合わせる方法を説明します。

主な機能

  1. 01Cache Components設定の検出
  2. 02'use cache'ディレクティブの利用ガイド
  3. 03キャッシュ期間とタグ無効化API
  4. 04静的シェルと動的ストリーミング
  5. 05ビルド時チェックとコードレビュー指針

活用シーン

  1. 01サーバー側のデータ取得へのキャッシュ追加
  2. 02キャッシュ済み・動的コンテンツを組み合わせたページ構成
  3. 03データ変更後のキャッシュ無効化
  4. 04Cache Componentsを使うコードベースのレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zoonk/zoonk --skill cache-components

インストール後、Claude Code で/cache-componentsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

PATTERNS.md
REFERENCE.md
TROUBLESHOOTING.md

詳細

Next.js Cache Components

自動有効化:next.config で cacheComponents: true が設定されているプロジェクトでは、このスキルが自動的に有効になります。

プロジェクトの検出

Next.js プロジェクトで作業を開始する際は、Cache Components が有効になっているか確認します。

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

cacheComponents: true が見つかった場合、以下の作業ではこのスキルのパターンを積極的に適用します。

  • React Server Components の記述
  • データ取得の実装
  • ミューテーションを行う Server Actions の作成
  • ページのパフォーマンス最適化
  • 既存のコンポーネントコードのレビュー

Cache Components は Partial Prerendering(PPR) を有効にし、静的 HTML シェルと動的なストリーミングコンテンツを組み合わせて、最適なパフォーマンスを実現します。

考え方:設定よりコード

Cache Components によって、セグメント設定から合成可能なコードへと移行します。

以前(非推奨)以後(Cache Components)
export const revalidate = 3600'use cache' 内の cacheLife('hours')
export const dynamic = 'force-static''use cache' と Suspense 境界を使用
静的か動的かの二者択一細分化:静的シェル + キャッシュ済み + 動的

重要な原則:コンポーネントはデータだけでなく、キャッシュ処理もその場にまとめます。Next.js はビルド時にフィードバックを提供し、最適なパターンを選べるようにします。

中核となる概念

┌─────────────────────────────────────────────────────┐
│                   Static Shell                       │
│  (Sent immediately to browser)                       │
│                                                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
│  │   Header    │  │  Cached     │  │  Suspense   │  │
│  │  (static)   │  │  Content    │  │  Fallback   │  │
│  └─────────────┘  └─────────────┘  └──────┬──────┘  │
│                                           │         │
│                                    ┌──────▼──────┐  │
│                                    │  Dynamic    │  │
│                                    │  (streams)  │  │
│                                    └─────────────┘  │
└─────────────────────────────────────────────────────┘

メンタルモデル:キャッシュの判断フロー

React Server Component を記述する際は、次の質問を順番に確認します。

┌─────────────────────────────────────────────────────────┐
│ Does this component fetch data or perform I/O?          │
└─────────────────────┬───────────────────────────────────┘
                      │
           ┌──────────▼──────────┐
           │   YES               │ NO → Pure component, no action needed
           └──────────┬──────────┘
                      │
    ┌─────────────────▼─────────────────┐
    │ Does it depend on request context? │
    │ (cookies, headers, searchParams)   │
    └─────────────────┬─────────────────┘
                      │
         ┌────────────┴────────────┐
         │                         │
    ┌────▼────┐              ┌─────▼─────┐
    │   YES   │              │    NO     │
    └────┬────┘              └─────┬─────┘
         │                         │
         │                   ┌─────▼─────────────────┐
         │                   │ Can this be cached?   │
         │                   │ (same for all users?) │
         │                   └─────┬─────────────────┘
         │                         │
         │              ┌──────────┴──────────┐
         │              │                     │
         │         ┌────▼────┐          ┌─────▼─────┐
         │         │   YES   │          │    NO     │
         │         └────┬────┘          └─────┬─────┘
         │              │                     │
         │              ▼                     │
         │         'use cache'                │
         │         + cacheTag()               │
         │                                    │
         └──────────────┬─────────────────────┘
                        │
                        ▼
              Wrap in <Suspense>
              (dynamic streaming)

重要なポイント:'use cache' ディレクティブは、ユーザー間で同一の データに使用します。ユーザー固有のデータは、Suspense を使って動的なままにします。

クイックスタート

Cache Components を有効にする
// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;
基本的な使い方
// Cached component - output included in static shell
async function CachedPosts() {
  "use cache";
  const posts = await db.posts.findMany();
  return <PostList posts={posts} />;
}

// Page with static + cached + dynamic content
export default async function BlogPage() {
  return (
    <>
      <Header /> {/* Static */}
      <CachedPosts /> {/* Cached */}
      <Suspense fallback={<Skeleton />}>
        <DynamicComments /> {/* Dynamic - streams */}
      </Suspense>
    </>
  );
}

中核となる API

1. 'use cache' ディレクティブ

コードをキャッシュ可能として指定します。次の3つのレベルで適用できます。

// File-level: All exports are cached
"use cache";
export async function getData() {
  /* ... */
}
export async function Component() {
  /* ... */
}

// Component-level
async function UserCard({ id }: { id: string }) {
  "use cache";
  const user = await fetchUser(id);
  return <Card>{user.name}</Card>;
}

// Function-level
async function fetchWithCache(url: string) {
  "use cache";
  return fetch(url).then((r) => r.json());
}

重要:キャッシュする関数はすべて async である必要があります。

2. cacheLife() - キャッシュ期間の制御
import { cacheLife } from "next/cache";

async function Posts() {
  "use cache";
  cacheLife("hours"); // Use a predefined profile

  // Or custom configuration:
  cacheLife({
    stale: 60, // 1 min - client cache validity
    revalidate: 3600, // 1 hr - start background refresh
    expire: 86400, // 1 day - absolute expiration
  });

  return await db.posts.findMany();
}

定義済みプロファイル:'default'、'seconds'、'minutes'、'hours'、'days'、'weeks'、'max'

3. cacheTag() - 無効化用のタグ
import { cacheTag } from "next/cache";

async function BlogPosts() {
  "use cache";
  cacheTag("posts");
  cacheLife("days");

  return await db.posts.findMany();
}

async function UserProfile({ userId }: { userId: string }) {
  "use cache";
  cacheTag("users", `user-${userId}`); // Multiple tags

  return await db.users.findUnique({ where: { id: userId } });
}
4. updateTag() - 即時の無効化

書き込み後の読み取りのセマンティクスに使用します。

"use server";
import { updateTag } from "next/cache";

export async function createPost(formData: FormData) {
  await db.posts.create({ data: formData });

  updateTag("posts"); // Client immediately sees fresh data
}
5. revalidateTag() - バックグラウンドでの再検証

stale-while-revalidate パターンに使用します。

"use server";
import { revalidateTag } from "next/cache";

export async function updatePost(id: string, data: FormData) {
  await db.posts.update({ where: { id }, data });

  revalidateTag("posts", "max"); // Serve stale, refresh in background
}

各パターンの使い分け

コンテンツの種類API動作
静的ディレクティブなしビルド時にレンダリング
キャッシュ済み'use cache'静的シェルに含まれ、再検証される
動的<Suspense> 内リクエスト時にストリーミングされる

パラメーターの組み合わせとサブシェル

重要な概念:Cache Components では、Next.js は指定されたパラメーターのすべての組み合わせをレンダリングし、再利用可能なサブシェルを作成します。

// app/products/[category]/[slug]/page.tsx
export async function generateStaticParams() {
  return [
    { category: "jackets", slug: "classic-bomber" },
    { category: "jackets", slug: "essential-windbreaker" },
    { category: "accessories", slug: "thermal-fleece-gloves" },
  ];
}

Next.js は次のルートをレンダリングします。

/products/jackets/classic-bomber        ← Full params (complete page)
/products/jackets/essential-windbreaker ← Full params (complete page)
/products/accessories/thermal-fleece-gloves ← Full params (complete page)
/products/jackets/[slug]                ← Partial params (category subshell)
/products/accessories/[slug]            ← Partial params (category subshell)
/products/[category]/[slug]             ← No params (fallback shell)

重要な理由:カテゴリーのサブシェル(/products/jackets/[slug])は、generateStaticParams に含まれていない商品も含め、任意のジャケット商品に再利用できます。一覧にないジャケットにユーザーが移動した場合、キャッシュ済みのカテゴリーシェルがすぐに表示され、商品の詳細はストリーミングで読み込まれます。

generateStaticParams の要件

Cache Components が有効な場合:

  1. 少なくとも1つのパラメーターを指定する必要があります - 空の配列はビルドエラーになります(本番環境でのサイレント障害を防止します)
  2. パラメーターによって静的に安全であることを証明します - パラメーターを指定すると、Next.js は動的 API が呼び出されていないことを検証できます
  3. 部分的なパラメーターによってサブシェルが作成されます - 一意の組み合わせごとに、再利用可能なシェルが生成されます
// ❌ ERROR with Cache Components
export function generateStaticParams() {
  return []; // Build error: must provide at least one param
}

// ✅ CORRECT: Provide real params
export async function generateStaticParams() {
  const products = await getPopularProducts();
  return products.map(({ category, slug }) => ({ category, slug }));
}

キャッシュキー = 引数

引数はキャッシュキーの一部になります。

// Different userId = different cache entry
async function UserData({ userId }: { userId: string }) {
  "use cache";
  cacheTag(`user-${userId}`);

  return await fetchUser(userId);
}

ビルド時のフィードバック

Cache Components は、開発中に早期のフィードバックを提供します。次のビルドエラーは、最適なパターンを選べるように導きます。

エラー:Suspense の外部に動的データがある
Error: Accessing cookies/headers/searchParams outside a Suspense boundary

解決策:動的なコンポーネントを <Suspense> で囲みます。

<Suspense fallback={<Skeleton />}>
  <ComponentThatUsesCookies />
</Suspense>
エラー:Suspense の外部にキャッシュされていないデータがあります
Error: Accessing uncached data outside Suspense

解決策:データをキャッシュするか、Suspense でラップしてください。

// Option 1: Cache it
async function ProductData({ id }: { id: string }) {
  "use cache";
  return await db.products.findUnique({ where: { id } });
}

// Option 2: Make it dynamic with Suspense
<Suspense fallback={<Loading />}>
  <DynamicProductData id={id} />
</Suspense>;
エラー:キャッシュ内でリクエストデータを使用しています
Error: Cannot access cookies/headers inside 'use cache'

解決策:ランタイムデータをキャッシュ境界の外に取り出してください(上記の「ランタイムデータの処理」を参照)。

追加リソース

  • API リファレンスの全容については、REFERENCE.md を参照してください
  • よく使われるパターンやレシピについては、PATTERNS.md を参照してください
  • デバッグとトラブルシューティングについては、TROUBLESHOOTING.md を参照してください

コード生成ガイドライン

Cache Component のコードを生成する際は、次の点に従ってください。

  1. 必ず async を使用する - キャッシュ対象の関数はすべて async である必要があります
  2. 'use cache' を先頭に置く - 関数本体の最初の文にする必要があります
  3. cacheLife() を早めに呼び出す - 'use cache' ディレクティブの直後に記述してください
  4. 意味のあるタグを付ける - 無効化の要件に合った意味的なタグを使用してください
  5. ランタイムデータを取り出す - cookies()/headers() をキャッシュ対象の範囲外に移動してください
  6. 動的コンテンツをラップする - キャッシュされない非同期コンポーネントには <Suspense> を使用してください

積極的な適用(Cache Components が有効な場合)

プロジェクトで cacheComponents: true が検出された場合は、これらのパターンを自動的に適用してください。

データ取得コンポーネントを記述する場合

「このデータはキャッシュできるか」と自問してください。できる場合は、'use cache' を追加してください。

// Before: Uncached fetch
async function ProductList() {
  const products = await db.products.findMany();
  return <Grid products={products} />;
}

// After: With caching
async function ProductList() {
  "use cache";
  cacheTag("products");
  cacheLife("hours");

  const products = await db.products.findMany();
  return <Grid products={products} />;
}
Server Actions を記述する場合

ミューテーション後は、常に関連するキャッシュを無効化してください。

"use server";
import { updateTag } from "next/cache";

export async function createProduct(data: FormData) {
  await db.products.create({ data });
  updateTag("products"); // Don't forget!
}
ページを構成する場合

静的シェル、キャッシュされたコンテンツ、動的ストリーミングを組み合わせて構成してください。

export default async function Page() {
  return (
    <>
      <StaticHeader /> {/* No cache needed */}
      <CachedContent /> {/* 'use cache' */}
      <Suspense fallback={<Skeleton />}>
        <DynamicUserContent /> {/* Streams at runtime */}
      </Suspense>
    </>
  );
}
コードをレビューする場合

Cache Components を使用するプロジェクトでは、次の問題を指摘してください。

  • キャッシュが有効なデータ取得で 'use cache' が使用されていない
  • cacheTag() の呼び出しがない(無効化ができなくなります)
  • Server Actions でミューテーション後に updateTag()/revalidateTag() が呼び出されていない
  • 'use cache' のスコープ内で cookies()/headers() が呼び出されている
  • 動的コンポーネントに <Suspense> 境界がない
  • 非推奨:export const revalidate - 'use cache' 内の cacheLife() に置き換えてください
  • 非推奨:export const dynamic - Suspense とキャッシュ境界に置き換えてください
  • generateStaticParams() の戻り値が空 - 少なくとも 1 つのパラメーターを指定する必要があります
注記

このプロジェクトでは、デフォルトの cacheLife に max プロファイルを設定しているため、キャッシュの有効期間をカスタマイズする必要がない限り、ほとんどの場合 cacheLife() を呼び出す必要はありません。

コメント