next-cache-components について
Next.js 16+ の Cache Components を扱う開発ガイドです。1 つのルートで静的・キャッシュ済み・動的コンテンツを組み合わせる方法を説明し、キャッシュ設定や旧 API からの移行に役立ちます。
主な機能
- 01静的・キャッシュ済み・動的コンテンツの併用
- 02use cache によるキャッシュ宣言
- 03キャッシュ期間とタグの設定
- 04即時無効化とバックグラウンド再検証
- 05旧設定と API からの移行
- 06ランタイム上の制約の確認
活用シーン
- 01ルートの部分プリレンダリング設定
- 02キャッシュデータの有効期間の設定
- 03データ更新後の関連キャッシュ無効化
- 04unstable_cache から use cache への移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ openreview --skill next-cache-componentsインストール後、Claude Code で/next-cache-componentsを入力して読み込まれたか確認します。
詳細
Cache Components(Next.js 16+)
Cache Components は Partial Prerendering(PPR)を有効にします。1 つのルート内で、静的コンテンツ、キャッシュされたコンテンツ、動的コンテンツを組み合わせられます。
Cache Components を有効にする
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
これは以前の experimental.ppr フラグに代わるものです。
3 種類のコンテンツ
Cache Components を有効にすると、コンテンツは次の 3 つのカテゴリーに分類されます。
1. 静的(自動的にプリレンダリング)
同期コード、インポート、純粋な計算は、ビルド時にプリレンダリングされます。
export default function Page() {
return (
<header>
<h1>Our Blog</h1> {/* Static - instant */}
<nav>...</nav>
</header>
)
}
2. キャッシュ対象(use cache)
リクエストごとに最新データを取得する必要のない非同期データです。
async function BlogPosts() {
'use cache'
cacheLife('hours')
const posts = await db.posts.findMany()
return <PostList posts={posts} />
}
3. 動的(Suspense)
常に最新である必要のある実行時データです。Suspense でラップします。
import { Suspense } from 'react'
export default function Page() {
return (
<>
<BlogPosts /> {/* Cached */}
<Suspense fallback={<p>Loading...</p>}>
<UserPreferences /> {/* Dynamic - streams in */}
</Suspense>
</>
)
}
async function UserPreferences() {
const theme = (await cookies()).get('theme')?.value
return <p>Theme: {theme}</p>
}
use cache ディレクティブ
ファイルレベル
'use cache'
export default async function Page() {
// Entire page is cached
const data = await fetchData()
return <div>{data}</div>
}
コンポーネントレベル
export async function CachedComponent() {
'use cache'
const data = await fetchData()
return <div>{data}</div>
}
関数レベル
export async function getData() {
'use cache'
return db.query('SELECT * FROM posts')
}
キャッシュプロファイル
組み込みプロファイル
'use cache' // Default: 5m stale, 15m revalidate
'use cache: remote' // Platform-provided cache (Redis, KV)
'use cache: private' // For compliance, allows runtime APIs
cacheLife() - カスタム有効期間
import { cacheLife } from 'next/cache'
async function getData() {
'use cache'
cacheLife('hours') // Built-in profile
return fetch('/api/data')
}
組み込みプロファイル:'default'、'minutes'、'hours'、'days'、'weeks'、'max'
インライン設定
async function getData() {
'use cache'
cacheLife({
stale: 3600, // 1 hour - serve stale while revalidating
revalidate: 7200, // 2 hours - background revalidation interval
expire: 86400, // 1 day - hard expiration
})
return fetch('/api/data')
}
キャッシュの無効化
cacheTag() - キャッシュされたコンテンツにタグを付ける
import { cacheTag } from 'next/cache'
async function getProducts() {
'use cache'
cacheTag('products')
return db.products.findMany()
}
async function getProduct(id: string) {
'use cache'
cacheTag('products', `product-${id}`)
return db.products.findUnique({ where: { id } })
}
updateTag() - 即時の無効化
同じリクエスト内でキャッシュを更新する必要がある場合に使用します。
'use server'
import { updateTag } from 'next/cache'
export async function updateProduct(id: string, data: FormData) {
await db.products.update({ where: { id }, data })
updateTag(`product-${id}`) // Immediate - same request sees fresh data
}
revalidateTag() - バックグラウンドでの再検証
stale-while-revalidate の動作を実現する場合に使用します。
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost(data: FormData) {
await db.posts.create({ data })
revalidateTag('posts') // Background - next request sees fresh data
}
実行時データの制約
use cache 内では、cookies()、headers()、searchParams にアクセスできません。
解決策:引数として渡す
// Wrong - runtime API inside use cache
async function CachedProfile() {
'use cache'
const session = (await cookies()).get('session')?.value // Error!
return <div>{session}</div>
}
// Correct - extract outside, pass as argument
async function ProfilePage() {
const session = (await cookies()).get('session')?.value
return <CachedProfile sessionId={session} />
}
async function CachedProfile({ sessionId }: { sessionId: string }) {
'use cache'
// sessionId becomes part of cache key automatically
const data = await fetchUserData(sessionId)
return <div>{data.name}</div>
}
例外:use cache: private
コードをリファクタリングできない場合に、コンプライアンス要件を満たすために使用します。
async function getData() {
'use cache: private'
const session = (await cookies()).get('session')?.value // Allowed
return fetchData(session)
}
キャッシュキーの生成
キャッシュキーは、次の要素に基づいて自動生成されます。
- ビルド ID - デプロイ時にすべてのキャッシュを無効化します
- 関数 ID - 関数の位置のハッシュ
- シリアライズ可能な引数 - props がキーの一部になります
- クロージャ変数 - 外側のスコープの値が含まれます
async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
'use cache'
// Cache key = userId (closure) + filter (argument)
return fetch(`/api/users/${userId}?filter=${filter}`)
}
return getData('active')
}
完全な例
import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
export default function DashboardPage() {
return (
<>
{/* Static shell - instant from CDN */}
<header><h1>Dashboard</h1></header>
<nav>...</nav>
{/* Cached - fast, revalidates hourly */}
<Stats />
{/* Dynamic - streams in with fresh data */}
<Suspense fallback={<NotificationsSkeleton />}>
<Notifications />
</Suspense>
</>
)
}
async function Stats() {
'use cache'
cacheLife('hours')
cacheTag('dashboard-stats')
const stats = await db.stats.aggregate()
return <StatsDisplay stats={stats} />
}
async function Notifications() {
const userId = (await cookies()).get('userId')?.value
const notifications = await db.notifications.findMany({
where: { userId, read: false }
})
return <NotificationList items={notifications} />
}
以前のバージョンからの移行
| 旧設定 | 置き換え |
|---|---|
experimental.ppr | cacheComponents: true |
dynamic = 'force-dynamic' | 削除(デフォルトの動作) |
dynamic = 'force-static' | 'use cache' + cacheLife('max') |
revalidate = N | cacheLife({ revalidate: N }) |
unstable_cache() | 'use cache' ディレクティブ |
unstable_cache から use cache への移行
Next.js 16 では、unstable_cache は use cache ディレクティブに置き換えられています。cacheComponents が有効な場合は、unstable_cache の呼び出しを use cache 関数に変換してください。
変更前(unstable_cache):
import { unstable_cache } from 'next/cache'
const getCachedUser = unstable_cache(
async (id) => getUser(id),
['my-app-user'],
{
tags: ['users'],
revalidate: 60,
}
)
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await getCachedUser(id)
return <div>{user.name}</div>
}
変更後(use cache):
import { cacheLife, cacheTag } from 'next/cache'
async function getCachedUser(id: string) {
'use cache'
cacheTag('users')
cacheLife({ revalidate: 60 })
return getUser(id)
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await getCachedUser(id)
return <div>{user.name}</div>
}
主な違い:
- 手動でのキャッシュキーが不要 -
use cacheは関数の引数とクロージャからキーを自動生成します。unstable_cacheのkeyParts配列は不要になりました。 - タグ -
options.tagsを、関数内でのcacheTag()の呼び出しに置き換えます。 - 再検証 -
options.revalidateを、cacheLife({ revalidate: N })またはcacheLife('minutes')のような組み込みプロファイルに置き換えます。 - 動的データ -
unstable_cacheではコールバック内でのcookies()やheaders()の使用はサポートされていません。use cacheにも同じ制約がありますが、必要に応じて'use cache: private'を使用できます。
制限事項
- Edge ランタイムはサポートされていません - Node.js が必要です
- 静的エクスポートはサポートされていません - サーバーが必要です
- 非決定的な値(
Math.random()、Date.now())は、use cache内ではビルド時に一度だけ実行されます
キャッシュの外でリクエスト時にランダムな値を生成する場合:
import { connection } from 'next/server'
async function DynamicContent() {
await connection() // Defer to request time
const id = crypto.randomUUID() // Different per request
return <div>{id}</div>
}
ソース: