Skip to content

包括的なキャッシング戦略

Next.jsのキャッシング戦略を包括的に理解し、実践的に活用する方法を詳しく解説します。

なぜキャッシング戦略が重要なのか

Section titled “なぜキャッシング戦略が重要なのか”

問題のある実装:

// キャッシングなし: 毎回APIリクエストが発生
export default async function ProductList() {
const products = await fetch('https://api.example.com/products');
const data = await products.json();
return (
<div>
{data.map(product => (
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
// 問題点:
// - 毎回APIリクエストが発生
// - サーバーの負荷が高い
// - レスポンス時間が遅い
// - ユーザー体験が悪い

影響:

  • サーバーの負荷増加
  • レスポンス時間の増加
  • ユーザー体験の低下
  • コストの増加

改善された実装:

// キャッシングあり: 効率的なデータ取得
export default async function ProductList() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1時間キャッシュ
});
const data = await products.json();
return (
<div>
{data.map(product => (
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
// メリット:
// - APIリクエストが削減される
// - サーバーの負荷が軽減される
// - レスポンス時間が短縮される
// - ユーザー体験が向上する

1. Request Memoization(リクエストメモ化)

Section titled “1. Request Memoization(リクエストメモ化)”

定義: 同一リクエスト内での重複したfetchリクエストを自動的にメモ化します。

実装例:

// 同一リクエスト内で複数回呼び出されても、1回だけ実行される
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
}
export default async function UserProfile({ params }: { params: { id: string } }) {
// これらの呼び出しは自動的にメモ化される
const user = await getUser(params.id);
const userPosts = await getUser(params.id); // 同じリクエストが再利用される
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}

メリット:

  • 自動的に重複リクエストを防ぐ
  • パフォーマンスの向上
  • コードの変更が不要

2. Data Cache(データキャッシュ)

Section titled “2. Data Cache(データキャッシュ)”

定義: fetchの結果を自動的にキャッシュし、同じリクエストに対してキャッシュから返します。

実装例:

// デフォルトでキャッシュされる
const res = await fetch('https://api.example.com/products');
const data = await res.json();
// キャッシュの制御
// 1. 時間ベースの再検証(ISR)
const res1 = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1時間ごとに再検証
});
// 2. キャッシュを無効化
const res2 = await fetch('https://api.example.com/products', {
cache: 'no-store' // キャッシュしない
});
// 3. 強制キャッシュ
const res3 = await fetch('https://api.example.com/products', {
cache: 'force-cache' // 強制的にキャッシュから取得
});

タグベースの再検証:

// タグを指定してキャッシュ
const res = await fetch('https://api.example.com/products', {
next: {
revalidate: 3600,
tags: ['products'] // タグでキャッシュを管理
}
});
// タグベースの再検証(API Route)
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const { tag } = await request.json();
revalidateTag(tag); // 特定のタグのキャッシュを無効化
return Response.json({ revalidated: true });
}

3. Full Route Cache(完全ルートキャッシュ)

Section titled “3. Full Route Cache(完全ルートキャッシュ)”

定義: 静的レンダリングされたページのレンダリング結果をキャッシュします。

実装例:

// 静的ページ: ビルド時にレンダリングされ、キャッシュされる
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: false } // キャッシュを無効化しない
});
const data = await post.json();
return (
<article>
<h1>{data.title}</h1>
<div>{data.content}</div>
</article>
);
}

動的ルートのキャッシュ:

// 動的ルート: キャッシュを無効化
export const dynamic = 'force-dynamic'; // 動的レンダリングを強制
export default async function Dashboard() {
const data = await fetch('https://api.example.com/dashboard', {
cache: 'no-store' // キャッシュしない
});
const dashboard = await data.json();
return <Dashboard data={dashboard} />;
}

4. Router Cache(ルーターキャッシュ)

Section titled “4. Router Cache(ルーターキャッシュ)”

定義: クライアント側でナビゲーション時にページをキャッシュします。

実装例:

next.config.js
// クライアント側のキャッシュ設定
module.exports = {
experimental: {
staleTimes: {
dynamic: 30, // 動的ページ: 30秒
static: 180, // 静的ページ: 180秒
},
},
};

1. 常に最新のデータが必要な場合

Section titled “1. 常に最新のデータが必要な場合”
// ダッシュボード: 常に最新のデータが必要
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export default async function Dashboard() {
const data = await fetch('https://api.example.com/dashboard', {
cache: 'no-store' // キャッシュしない
});
const dashboard = await data.json();
return <Dashboard data={dashboard} />;
}
// 商品一覧: 1時間ごとに更新
export default async function Products() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1時間ごとに再検証
});
const products = await res.json();
return <ProductList products={products} />;
}

3. ビルド時に生成されるデータ

Section titled “3. ビルド時に生成されるデータ”
// ブログ記事: ビルド時に生成
export default async function BlogPost({ params }: { params: { slug: string } }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: false } // キャッシュを無効化しない
});
const post = await res.json();
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
// 商品詳細: 更新時に再検証
export default async function Product({ params }: { params: { id: string } }) {
const res = await fetch(`https://api.example.com/products/${params.id}`, {
next: {
revalidate: 3600,
tags: [`product-${params.id}`] // 商品ごとにタグを設定
}
});
const product = await res.json();
return <ProductDetails product={product} />;
}
// API Route: 商品更新時に再検証
// app/api/products/[id]/route.ts
import { revalidateTag } from 'next/cache';
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
) {
// 商品を更新
await updateProduct(params.id, await request.json());
// キャッシュを再検証
revalidateTag(`product-${params.id}`);
return Response.json({ success: true });
}
データの特性推奨戦略実装例
常に最新が必要cache: 'no-store'ダッシュボード、リアルタイムデータ
定期的に更新revalidate: 3600商品一覧、ニュース記事
ビルド時に生成revalidate: falseブログ記事、ドキュメント
更新時に再検証タグベース再検証商品詳細、ユーザープロフィール

パフォーマンスとデータの鮮度のバランス

Section titled “パフォーマンスとデータの鮮度のバランス”
// パフォーマンス重視: 長いキャッシュ時間
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 86400 } // 24時間
});
// データの鮮度重視: 短いキャッシュ時間
const res = await fetch('https://api.example.com/dashboard', {
next: { revalidate: 60 } // 1分
});
// バランス: 中程度のキャッシュ時間
const res = await fetch('https://api.example.com/news', {
next: { revalidate: 3600 } // 1時間
});

包括的なキャッシング戦略のポイント:

  • Request Memoization: 同一リクエスト内での重複リクエストを防ぐ
  • Data Cache: fetchの結果をキャッシュ、時間ベース・タグベースの再検証
  • Full Route Cache: 静的レンダリング結果のキャッシュ
  • Router Cache: クライアント側のルーターキャッシュ
  • 実践的な戦略: データの特性に応じた選択、パフォーマンスとデータの鮮度のバランス

適切にキャッシング戦略を使用することで、パフォーマンスとデータの鮮度のバランスを最適化できます。