App Router では、デフォルトが静的化(キャッシュ)であること、fetch による
キャッシュと ISR の制御、dynamic / revalidate / noStore の
組み合わせが要点です。落とし穴と設計パターンをまとめます。
基本の制御フラグ
- 静的化: 既定は
force-cache。export const revalidate = 3600で ISR - 動的化: ルートで
export const dynamic = 'force-dynamic'/revalidate = 0 - noStore:
import { noStore } from 'next/cache'で関数内キャッシュを回避
// ルート単位の既定
export const revalidate = 3600 // 1時間ごとに再生成(ISR)
// export const dynamic = 'force-dynamic' // 完全動的にしたい場合
// 関数内でキャッシュ回避
import { noStore } from 'next/cache'
export async function getData() {
noStore()
const res = await fetch('https://api.example.com', { cache: 'no-store' })
return res.json()
}
fetch のキャッシュ戦略
cache: 'force-cache': ビルド or 初回アクセスでキャッシュ作成(既定)next: { revalidate: 秒 }: リクエスト単位で ISRcache: 'no-store': 常に最新(動的)。API 叩きすぎに注意
// リクエスト単位の ISR 指定
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 600 }, // 10分
})
const posts = await res.json()
タグ/パスの再検証
ミューテーション後に対象だけを再生成するなら、タグやパスでの再検証が有効です。
import { revalidatePath, revalidateTag } from 'next/cache'
// タグ付けして取得
await fetch('https://api.example.com/posts', { next: { tags: ['posts'] } })
// 変更後に再検証
await revalidateTag('posts')
// or ページ単位
await revalidatePath('/blog')
Edge と Node の使い分け
- 低レイテンシや地理分散:
export const runtime = 'edge' - Node API 依存/重量処理:
runtime = 'nodejs'を明示 - サーバレスな RDB 接続は serverless driver を検討
// ルート直下の設定
export const runtime = 'edge' // or 'nodejs'
落とし穴とチェックリスト
- RSC の fetch は自動重複排除される(同一入力を 1 回に統合)
- クライアントコンポーネント内の fetch はブラウザ側で実行(キャッシュ戦略は別物)
- Secret/DB 接続はサーバーコンポーネント/Route Handler に閉じる
- ページ速度: キャッシュを安易に無効化しない。ISR かタグ再検証を選ぶ
まとめ: 既定は静的、変わる所だけ ISR/再検証、
常時最新が必要な箇所のみ no-store。
これが App Router の現実解です。