Next.js Server Components 時代のデータ取得戦略:fetch キャッシュと再検証の新常識
Next.js 13 以降、App Router と Server Components の登場により、データ取得の方法が大きく変わりました。従来の getServerSideProps や getStaticProps に代わり、コンポーネント内で直接 fetch を使ったデータ取得が可能になりましたが、それに伴いキャッシュと再検証の仕組みも刷新されています。本記事では、Server Components 時代における fetch のキャッシュ戦略と再検証の新しい考え方について、初心者の方にもわかりやすく解説します。
背景
Server Components の登場で変わったデータ取得
Next.js の App Router では、デフォルトでコンポーネントが Server Components として動作します。これにより、サーバー側で直接データベースにアクセスしたり、API を呼び出したりできるようになりました。
従来の Pages Router では、ページレベルでのみデータ取得が可能でしたが、Server Components ではコンポーネント単位でのデータ取得が実現できます。この変化により、よりきめ細かいデータ管理とキャッシュ戦略が必要になってきました。
以下の図は、Pages Router と App Router におけるデータ取得の違いを示しています。
mermaidflowchart TB
subgraph old["Pages Router(従来)"]
page1["ページ"] -->|getServerSideProps| data1["データ取得"]
data1 --> comp1["コンポーネント A"]
data1 --> comp2["コンポーネント B"]
end
subgraph new["App Router(新しい)"]
page2["ページ"] --> sc1["Server Component A<br/>(直接データ取得)"]
page2 --> sc2["Server Component B<br/>(直接データ取得)"]
sc1 -->|fetch| api1["API/DB"]
sc2 -->|fetch| api2["API/DB"]
end
図で理解できる要点:
- Pages Router ではページ単位でのデータ取得が必須でした
- App Router では各コンポーネントが独立してデータを取得できます
- より柔軟で細かいデータ管理が可能になりました
fetch の拡張機能
Next.js 13 以降では、標準の fetch API が拡張され、Next.js 独自のキャッシュ機能が組み込まれています。
これにより、以下のような恩恵を受けられます。
| # | 機能 | 説明 |
|---|---|---|
| 1 | 自動キャッシュ | デフォルトでリクエストがキャッシュされる |
| 2 | リクエスト重複排除 | 同じリクエストは自動的に 1 回にまとめられる |
| 3 | 柔軟な再検証 | 時間ベースやオンデマンドでキャッシュを更新できる |
| 4 | セグメント単位の制御 | ルートセグメントごとにキャッシュ戦略を設定可能 |
課題
従来のデータ取得における問題点
Pages Router の時代には、いくつかの課題がありました。
まず、ページレベルでしかデータ取得ができないため、複数のコンポーネントで異なるデータが必要な場合、すべてを一度に取得する必要がありました。これにより、不要なデータまで取得してしまい、パフォーマンスに影響を与えることがありました。
また、getStaticProps でキャッシュする場合は、再検証のタイミングが限定的で、リアルタイム性が求められるデータには不向きでした。一方、getServerSideProps を使うとリクエストごとにデータを取得するため、サーバー負荷が高くなります。
キャッシュ戦略の複雑さ
Server Components の登場により柔軟なデータ取得が可能になった反面、適切なキャッシュ戦略を選ばないとパフォーマンス問題や古いデータの表示といった新たな課題が生まれます。
以下の図は、キャッシュ戦略の選択が難しい理由を示しています。
mermaidflowchart TD
start["データ取得の要件"] --> q1{データの<br/>更新頻度は?}
q1 -->|高頻度| realtime["リアルタイム性<br/>が必要"]
q1 -->|低頻度| static_data["静的データ<br/>で十分"]
realtime --> q2{キャッシュ<br/>は必要?}
q2 -->|必要| short_cache["短時間キャッシュ"]
q2 -->|不要| no_cache["キャッシュなし"]
static_data --> q3{更新タイミング<br/>は?}
q3 -->|定期的| revalidate["時間ベース再検証"]
q3 -->|不定期| on_demand["オンデマンド再検証"]
short_cache --> problem1["パフォーマンスと<br/>新鮮さのバランス"]
no_cache --> problem2["サーバー負荷増大"]
revalidate --> problem3["最適な間隔の決定"]
on_demand --> problem4["再検証のタイミング<br/>管理"]
図で理解できる要点:
- データの性質によって最適なキャッシュ戦略が異なります
- それぞれの戦略には固有の課題があります
- パフォーマンスと新鮮さのバランスが重要です
よくある誤解と落とし穴
Next.js の新しいキャッシュシステムについて、以下のような誤解がよくあります。
| # | 誤解 | 実際 |
|---|---|---|
| 1 | fetch は常にキャッシュされる | 動的レンダリングでは無効化されることがある |
| 2 | キャッシュは永続的 | ビルド時や再検証時にクリアされる |
| 3 | すべてのリクエストが重複排除される | 同一レンダリング内でのみ有効 |
| 4 | revalidate を設定すれば常に新しい | 次のリクエストまで古いデータが返る場合がある |
これらの誤解は、予期しない動作やパフォーマンス問題を引き起こす原因となります。
解決策
Next.js fetch のキャッシュオプション
Next.js の拡張 fetch には、3 つの主要なキャッシュ制御オプションがあります。それぞれを正しく理解し、使い分けることが重要です。
cache オプション
cache オプションは、リクエストのキャッシュ動作を制御します。
typescript// force-cache: デフォルト動作、キャッシュを利用
fetch('https://api.example.com/data', {
cache: 'force-cache',
});
上記コードは、データを強制的にキャッシュします。一度取得したデータは、再検証されるまで再利用されます。
typescript// no-store: キャッシュを使用せず、毎回新しいデータを取得
fetch('https://api.example.com/realtime', {
cache: 'no-store',
});
こちらは、リアルタイム性が重要なデータに適しています。毎回サーバーにリクエストが送られるため、常に最新のデータが取得できます。
next.revalidate オプション
next.revalidate オプションでは、時間ベースの再検証を設定します。
typescript// 60秒ごとにキャッシュを再検証
fetch('https://api.example.com/news', {
next: { revalidate: 60 },
});
このコードでは、60 秒経過後の次のリクエストでデータが再取得されます。ISR(Incremental Static Regeneration)と同様の動作です。
typescript// 1時間ごとに再検証
fetch('https://api.example.com/articles', {
next: { revalidate: 3600 },
});
更新頻度が低いデータには、長めの再検証間隔を設定することでサーバー負荷を軽減できます。
next.tags オプション
next.tags オプションは、オンデマンド再検証のためのタグを設定します。
typescript// タグ付きでデータを取得
fetch('https://api.example.com/posts/123', {
next: { tags: ['post', 'post-123'] },
});
タグを設定しておくことで、後から特定のデータのみを再検証できます。
キャッシュ戦略の使い分け
データの性質に応じて、適切なキャッシュ戦略を選択する必要があります。以下の表を参考にしてください。
| # | データの種類 | 推奨戦略 | 設定例 |
|---|---|---|---|
| 1 | 静的コンテンツ(会社情報など) | force-cache(デフォルト) | cache: 'force-cache' |
| 2 | 定期更新コンテンツ(ブログ記事) | 時間ベース再検証 | next: { revalidate: 3600 } |
| 3 | ユーザー依存データ(マイページ) | no-store | cache: 'no-store' |
| 4 | CMS コンテンツ | タグベース再検証 | next: { tags: ['cms'] } |
| 5 | リアルタイムデータ(株価など) | no-store | cache: 'no-store' |
オンデマンド再検証の実装
オンデマンド再検証は、必要なタイミングでキャッシュを更新する強力な機能です。
以下の図は、オンデマンド再検証の流れを示しています。
mermaidsequenceDiagram
participant Admin as 管理者
participant API as API Route
participant Next as Next.js
participant Cache as キャッシュ
participant User as ユーザー
Admin->>API: コンテンツ更新
API->>Next: revalidateTag('post')
Next->>Cache: タグに紐づく<br/>キャッシュを無効化
Cache-->>Next: 無効化完了
Next-->>API: 成功レスポンス
API-->>Admin: 更新完了
User->>Next: ページアクセス
Next->>Cache: キャッシュ確認
Cache-->>Next: キャッシュなし
Next->>Next: 新しいデータ取得
Next->>Cache: 新しいキャッシュ保存
Next-->>User: 最新ページ表示
図で理解できる要点:
- 管理者がコンテンツを更新すると、即座にキャッシュが無効化されます
- ユーザーの次のアクセス時に新しいデータが取得されます
- CMS 連携など、コンテンツ更新が不定期な場合に最適です
パスベースの再検証
特定のパスのキャッシュを再検証する方法です。
typescript// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
import { NextRequest } from 'next/server';
まず、必要なモジュールをインポートします。revalidatePath は特定のパスのキャッシュを再検証する関数です。
typescriptexport async function POST(request: NextRequest) {
// 認証トークンの確認
const token = request.headers.get('authorization')
if (token !== process.env.REVALIDATE_TOKEN) {
return Response.json(
{ message: 'Invalid token' },
{ status: 401 }
)
}
セキュリティのため、環境変数で設定したトークンを確認します。これにより、認証されたリクエストのみがキャッシュを再検証できます。
typescript// パスを取得
const path = request.nextUrl.searchParams.get('path');
if (!path) {
return Response.json(
{ message: 'Path is required' },
{ status: 400 }
);
}
再検証するパスをクエリパラメータから取得します。
typescript // パスのキャッシュを再検証
revalidatePath(path)
return Response.json({
revalidated: true,
now: Date.now(),
path: path
})
}
revalidatePath を実行して、指定されたパスのキャッシュを無効化します。次回のアクセス時に新しいデータが取得されます。
タグベースの再検証
より柔軟な再検証が可能なタグベースの方法です。
typescript// app/api/revalidate-tag/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
revalidateTag を使用すると、タグに紐づくすべてのキャッシュを一括で再検証できます。
typescriptexport async function POST(request: NextRequest) {
const token = request.headers.get('authorization')
if (token !== process.env.REVALIDATE_TOKEN) {
return Response.json(
{ message: 'Invalid token' },
{ status: 401 }
)
}
パスベースと同様に、認証チェックを行います。
typescript// タグを取得
const tag = request.nextUrl.searchParams.get('tag');
if (!tag) {
return Response.json(
{ message: 'Tag is required' },
{ status: 400 }
);
}
再検証するタグをクエリパラメータから取得します。
typescript // タグに紐づくすべてのキャッシュを再検証
revalidateTag(tag)
return Response.json({
revalidated: true,
now: Date.now(),
tag: tag
})
}
revalidateTag を実行すると、指定されたタグが付いたすべての fetch リクエストのキャッシュが無効化されます。複数のページやコンポーネントで同じデータを使用している場合に便利です。
ルートセグメント設定
ルートセグメント単位でキャッシュ動作を制御することもできます。
typescript// app/dashboard/layout.tsx または page.tsx
// すべての fetch をキャッシュしない
export const dynamic = 'force-dynamic';
dynamic オプションを force-dynamic に設定すると、そのルートセグメント内のすべての fetch が cache: 'no-store' と同じ動作になります。
typescript// デフォルトの再検証時間を設定
export const revalidate = 3600; // 1時間
ルートセグメント全体に対して、デフォルトの再検証時間を設定できます。個別の fetch で設定を上書きすることも可能です。
typescript// ランタイムをEdgeに設定
export const runtime = 'edge';
Edge Runtime を使用すると、世界中のエッジロケーションでコードが実行され、レイテンシが低減されます。
これらの設定により、ページやレイアウト単位でキャッシュ戦略を統一できます。
具体例
ブログサイトでの実装例
実際のブログサイトを想定した実装例を見ていきましょう。
記事一覧ページ
記事一覧は定期的に更新されるため、時間ベースの再検証が適しています。
typescript// app/blog/page.tsx
import Link from 'next/link';
// 型定義
interface Post {
id: number;
title: string;
excerpt: string;
publishedAt: string;
}
まず、記事データの型を定義します。TypeScript を使うことで、型安全なコードが書けます。
typescript// 記事一覧を取得する関数
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://api.example.com/posts', {
next: {
revalidate: 600, // 10分ごとに再検証
tags: ['posts'], // タグも設定
},
});
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
10 分ごとに再検証することで、パフォーマンスと新鮮さのバランスを取っています。タグも設定しておくことで、必要に応じてオンデマンド再検証も可能です。
typescript// Server Component
export default async function BlogPage() {
const posts = await getPosts();
return (
<div>
<h1>ブログ記事一覧</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.id}`}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<time>{post.publishedAt}</time>
</Link>
</li>
))}
</ul>
</div>
);
}
Server Component では、トップレベルで await を使ってデータを取得できます。取得したデータはそのままレンダリングに使用できます。
記事詳細ページ
記事詳細ページでは、より細かい制御が必要です。
typescript// app/blog/[id]/page.tsx
// 型定義
interface Post {
id: number;
title: string;
content: string;
author: {
name: string;
avatar: string;
};
publishedAt: string;
}
interface Comment {
id: number;
author: string;
content: string;
createdAt: string;
}
記事とコメントの型を定義します。
typescript// 記事本体を取得(キャッシュあり)
async function getPost(id: string): Promise<Post> {
const res = await fetch(
`https://api.example.com/posts/${id}`,
{
next: {
revalidate: 3600, // 1時間ごとに再検証
tags: ['post', `post-${id}`], // 複数のタグを設定
},
}
);
if (!res.ok) {
throw new Error('Failed to fetch post');
}
return res.json();
}
記事本体は頻繁に更新されないため、1 時間の再検証間隔を設定しています。タグは post と post-{id} の 2 つを設定することで、全記事または特定の記事のみを再検証できます。
typescript// コメントを取得(キャッシュなし)
async function getComments(
postId: string
): Promise<Comment[]> {
const res = await fetch(
`https://api.example.com/posts/${postId}/comments`,
{
cache: 'no-store', // 常に最新のコメントを表示
}
);
if (!res.ok) {
throw new Error('Failed to fetch comments');
}
return res.json();
}
コメントは頻繁に追加されるため、キャッシュを使用せず常に最新のデータを取得します。
typescript// ページコンポーネント
export default async function PostPage({
params
}: {
params: { id: string }
}) {
// 記事とコメントを並行取得
const [post, comments] = await Promise.all([
getPost(params.id),
getComments(params.id)
])
Promise.all を使うことで、記事とコメントを並行して取得できます。これにより、取得時間を短縮できます。
typescript return (
<article>
<header>
<h1>{post.title}</h1>
<div>
<img src={post.author.avatar} alt={post.author.name} />
<span>{post.author.name}</span>
<time>{post.publishedAt}</time>
</div>
</header>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
記事本体をレンダリングします。HTML コンテンツの場合は dangerouslySetInnerHTML を使用しますが、XSS 対策のため、サーバー側でサニタイズされたコンテンツであることを確認してください。
typescript <section>
<h2>コメント ({comments.length}件)</h2>
<ul>
{comments.map(comment => (
<li key={comment.id}>
<strong>{comment.author}</strong>
<p>{comment.content}</p>
<time>{comment.createdAt}</time>
</li>
))}
</ul>
</section>
</article>
)
}
コメント一覧を表示します。コメントは常に最新のデータが表示されるため、ユーザーがコメントを投稿した直後でも正しく表示されます。
EC サイトでの実装例
EC サイトでは、商品データと在庫データで異なるキャッシュ戦略が必要です。
以下の図は、EC サイトにおけるデータ取得とキャッシュ戦略の全体像を示しています。
mermaidflowchart TB
subgraph page["商品詳細ページ"]
sc1["Server Component<br/>(商品情報)"]
sc2["Server Component<br/>(在庫情報)"]
sc3["Server Component<br/>(レビュー)"]
end
sc1 -->|"revalidate: 3600<br/>tags: ['product']"| api1["商品 API<br/>(1時間キャッシュ)"]
sc2 -->|"cache: 'no-store'"| api2["在庫 API<br/>(キャッシュなし)"]
sc3 -->|"revalidate: 600<br/>tags: ['review']"| api3["レビュー API<br/>(10分キャッシュ)"]
admin["管理画面"] -->|商品更新| webhook["Webhook"]
webhook -->|revalidateTag| sc1
webhook -->|revalidateTag| sc3
図で理解できる要点:
- 商品情報は長めのキャッシュで効率化
- 在庫情報はリアルタイム性を重視してキャッシュなし
- レビューは短めのキャッシュでバランスを取る
- 管理画面からの更新時はオンデマンド再検証
商品詳細ページ
typescript// app/products/[id]/page.tsx
// 型定義
interface Product {
id: string;
name: string;
description: string;
price: number;
images: string[];
category: string;
}
interface Stock {
available: boolean;
quantity: number;
}
interface Review {
id: number;
author: string;
rating: number;
comment: string;
createdAt: string;
}
商品、在庫、レビューの型を定義します。
typescript// 商品情報を取得(長めのキャッシュ)
async function getProduct(id: string): Promise<Product> {
const res = await fetch(
`https://api.example.com/products/${id}`,
{
next: {
revalidate: 3600, // 1時間
tags: ['product', `product-${id}`],
},
}
);
if (!res.ok) {
throw new Error('Failed to fetch product');
}
return res.json();
}
商品情報は頻繁に変更されないため、1 時間のキャッシュを設定します。
typescript// 在庫情報を取得(キャッシュなし)
async function getStock(productId: string): Promise<Stock> {
const res = await fetch(
`https://api.example.com/products/${productId}/stock`,
{
cache: 'no-store', // 常に最新の在庫を確認
}
);
if (!res.ok) {
throw new Error('Failed to fetch stock');
}
return res.json();
}
在庫情報はリアルタイム性が重要なため、キャッシュを使用しません。これにより、売り切れ商品の購入を防げます。
typescript// レビューを取得(短めのキャッシュ)
async function getReviews(
productId: string
): Promise<Review[]> {
const res = await fetch(
`https://api.example.com/products/${productId}/reviews`,
{
next: {
revalidate: 600, // 10分
tags: ['review', `review-${productId}`],
},
}
);
if (!res.ok) {
throw new Error('Failed to fetch reviews');
}
return res.json();
}
レビューは定期的に追加されるため、10 分の再検証間隔を設定します。
typescript// ページコンポーネント
export default async function ProductPage({
params
}: {
params: { id: string }
}) {
// すべてのデータを並行取得
const [product, stock, reviews] = await Promise.all([
getProduct(params.id),
getStock(params.id),
getReviews(params.id)
])
3 つのデータソースを並行して取得することで、パフォーマンスを最適化します。
typescript return (
<div>
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>価格: ¥{product.price.toLocaleString()}</p>
{/* 在庫状況 */}
{stock.available ? (
<div>
<span>在庫あり</span>
<span>残り{stock.quantity}点</span>
<button>カートに追加</button>
</div>
) : (
<div>
<span>売り切れ</span>
</div>
)}
</div>
在庫情報に基づいて、カートに追加ボタンの表示を切り替えます。
typescript {/* レビュー */}
<section>
<h2>カスタマーレビュー</h2>
<div>
{reviews.map(review => (
<div key={review.id}>
<strong>{review.author}</strong>
<span>★{review.rating}</span>
<p>{review.comment}</p>
<time>{review.createdAt}</time>
</div>
))}
</div>
</section>
</div>
)
}
レビュー一覧を表示します。10 分ごとに更新されるため、新しいレビューも適度なタイミングで反映されます。
CMS 連携での実装例
CMS からコンテンツを取得する場合、Webhook と組み合わせたオンデマンド再検証が効果的です。
CMS データ取得
typescript// lib/cms.ts
// 型定義
interface Article {
id: string;
title: string;
content: string;
slug: string;
publishedAt: string;
updatedAt: string;
}
CMS の記事データの型を定義します。
typescript// CMSから記事一覧を取得
export async function getArticles(): Promise<Article[]> {
const res = await fetch(
'https://cms.example.com/api/articles',
{
headers: {
Authorization: `Bearer ${process.env.CMS_API_KEY}`,
},
next: {
tags: ['articles'],
},
}
);
if (!res.ok) {
throw new Error('Failed to fetch articles');
}
return res.json();
}
CMS API にアクセスする際は、認証ヘッダーを付与します。環境変数で API キーを管理することで、セキュリティを確保します。
typescript// 個別記事を取得
export async function getArticle(
slug: string
): Promise<Article> {
const res = await fetch(
`https://cms.example.com/api/articles/${slug}`,
{
headers: {
Authorization: `Bearer ${process.env.CMS_API_KEY}`,
},
next: {
tags: ['article', `article-${slug}`],
},
}
);
if (!res.ok) {
throw new Error('Failed to fetch article');
}
return res.json();
}
記事ごとに個別のタグを設定することで、特定の記事のみを再検証できます。
Webhook エンドポイント
CMS からの Webhook を受け取って、キャッシュを再検証します。
typescript// app/api/webhook/cms/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
必要なモジュールをインポートします。
typescript// Webhook署名の検証
function verifyWebhookSignature(
payload: string,
signature: string
): boolean {
// 実際の実装では、CMSプロバイダーが提供する
// 署名検証ロジックを使用してください
const expectedSignature = // ... 署名を計算
return signature === expectedSignature
}
Webhook のセキュリティを確保するため、署名を検証します。これにより、正規の CMS からのリクエストのみを受け付けます。
typescriptexport async function POST(request: NextRequest) {
try {
// リクエストボディを取得
const payload = await request.text()
const signature = request.headers.get('x-webhook-signature')
// 署名を検証
if (!signature || !verifyWebhookSignature(payload, signature)) {
return Response.json(
{ error: 'Invalid signature' },
{ status: 401 }
)
}
署名が無効な場合は、401 エラーを返します。
typescript// ペイロードをパース
const data = JSON.parse(payload);
const { event, article } = data;
Webhook のペイロードから、イベントタイプと記事情報を取得します。
typescript// イベントに応じて再検証
switch (event) {
case 'article.created':
case 'article.updated':
// 個別記事と一覧を再検証
revalidateTag(`article-${article.slug}`);
revalidateTag('articles');
break;
case 'article.deleted':
// 一覧のみ再検証
revalidateTag('articles');
break;
}
イベントタイプに応じて、適切なタグを再検証します。記事の作成や更新時は、その記事と一覧の両方を再検証します。
typescript return Response.json({
revalidated: true,
now: Date.now()
})
} catch (error) {
console.error('Webhook error:', error)
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
エラーが発生した場合は、ログに記録して 500 エラーを返します。
記事ページ
typescript// app/articles/[slug]/page.tsx
import { getArticle } from '@/lib/cms';
export default async function ArticlePage({
params,
}: {
params: { slug: string };
}) {
const article = await getArticle(params.slug);
return (
<article>
<h1>{article.title}</h1>
<time>公開日: {article.publishedAt}</time>
<time>更新日: {article.updatedAt}</time>
<div
dangerouslySetInnerHTML={{
__html: article.content,
}}
/>
</article>
);
}
CMS からデータを取得して表示します。CMS で記事を更新すると、Webhook 経由で自動的にキャッシュが再検証され、最新の内容が表示されます。
パフォーマンス最適化のポイント
実装時に意識すべきパフォーマンス最適化のポイントを紹介します。
| # | ポイント | 説明 | 効果 |
|---|---|---|---|
| 1 | 並行データ取得 | Promise.all で複数の fetch を同時実行 | 取得時間の短縮 |
| 2 | 適切なキャッシュ戦略 | データの性質に応じた cache オプション選択 | サーバー負荷軽減 |
| 3 | タグの活用 | 関連するデータに同じタグを設定 | 効率的な再検証 |
| 4 | ストリーミング | Suspense を使った段階的レンダリング | 初期表示の高速化 |
| 5 | エラーハンドリング | Error Boundary での適切なエラー処理 | ユーザー体験の向上 |
これらのポイントを意識することで、高速で信頼性の高いアプリケーションを構築できます。
まとめ
Next.js の App Router と Server Components により、データ取得とキャッシュ戦略が大きく進化しました。本記事で解説した内容をまとめます。
キャッシュオプションの使い分けが最も重要です。cache: 'force-cache' はデフォルトで静的コンテンツに、cache: 'no-store' はリアルタイム性が必要なデータに使用します。next.revalidate では時間ベースの再検証を設定でき、定期的に更新されるコンテンツに最適です。
オンデマンド再検証は、CMS 連携や管理画面からのコンテンツ更新時に威力を発揮します。revalidatePath でパス単位、revalidateTag でタグ単位の再検証が可能で、Webhook と組み合わせることで自動的なキャッシュ更新が実現できます。
データの性質に応じた戦略選択が成功の鍵となります。商品情報は長めのキャッシュ、在庫情報はキャッシュなし、レビューは短めのキャッシュというように、各データの特性を理解して適切な設定を行うことで、パフォーマンスとユーザー体験の両立が可能になります。
Server Components の登場により、より柔軟で効率的なデータ取得が可能になりました。本記事で紹介したパターンを参考に、プロジェクトに最適なキャッシュ戦略を構築してください。最初は試行錯誤が必要かもしれませんが、データの性質を見極めて適切な設定を行うことで、高速で快適なアプリケーションを実現できるでしょう。
関連リンク
articleNext.js Server Components 時代のデータ取得戦略:fetch キャッシュと再検証の新常識
articleNext.js の 観測可能性入門:OpenTelemetry/Sentry/Vercel Analytics 連携
articleNext.js でドキュメントポータル:MDX/全文検索/バージョン切替の設計例
articleNext.js でインフィニットスクロールを実装:Route Handlers +`use` で滑らかデータ読込
articleRedis 使い方:Next.js で Cache-Tag と再検証を実装(Edge/Node 両対応)
articleNext.js の RSC 境界設計:Client Components を最小化する責務分離戦略
articleNext.js Server Components 時代のデータ取得戦略:fetch キャッシュと再検証の新常識
articleReact Server Components 時代に Jotai はどう進化する?サーバーとクライアントをまたぐ状態管理の未来
articleReact Suspense × Server Componentsを使って実現するクライアントとサーバの責務分離
articleNuxt × Vercel/Netlify/Cloudflare:デプロイ先で変わる性能とコストを実測
articleRemix で「Hydration failed」を解決:サーバ/クライアント不整合の診断手順
articlePreact 本番最適化運用:Lighthouse 95 点超えのビルド設定と監視 KPI
articleNginx microcaching vs 上流キャッシュ(Varnish/Redis)比較:TTFB と整合性の最適解
articleNestJS × TypeORM vs Prisma vs Drizzle:DX・性能・移行性の総合ベンチ
articlePlaywright × Allure レポート運用:履歴・トレンド・失敗分析を見える化する
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来