Next.js 15でfetchのキャッシュデフォルトが変更 - no-storeへの対応方法

問題

Next.js 14から15にアップグレードしたところ、ページの読み込みが目に見えて遅くなりました。ネットワークタブを確認すると、同じAPIを毎回新しくリクエストしていました。

// このコード、Next.js 14では自動的にキャッシュされていました
const res = await fetch('https://api.example.com/products');
const data = await res.json();

コードは何も変えていないのに、フレームワークのデフォルト値が変わっていたのです。

原因

Next.js 15からfetch()のデフォルトcacheオプションが変更されました。

バージョン デフォルト値 動作
Next.js 14以下 force-cache 一度取得したらキャッシュ
Next.js 15以上 no-store 毎回新しくリクエスト

Vercelチームがこの変更を行った理由は、暗黙的なキャッシュによる「なぜデータが更新されないのか?」というバグが多すぎたためです。明示的に指定する方が安全という判断です。

解決方法

3つのアプローチがあります。

1. fetch単位でキャッシュを指定

// 永続キャッシュ(ビルド時のデータ)
const res = await fetch('https://api.example.com/products', {
  cache: 'force-cache'
});

// 時間ベースのキャッシュ(1時間ごとに更新)
const res = await fetch('https://api.example.com/products', {
  next: { revalidate: 3600 }
});

2. ルートセグメント設定

ページ全体を静的にしたい場合はこちらです。

// app/products/page.tsx
export const dynamic = 'force-static';
export const revalidate = 3600; // 1時間

export default async function ProductsPage() {
  const res = await fetch('https://api.example.com/products');
  // このページの全てのfetchがキャッシュされます
}

3. タグベースのキャッシュ無効化

Server Actionから特定のデータだけを更新する場合に使います。

// データ取得時にタグを指定
const res = await fetch('https://api.example.com/products', {
  next: { tags: ['products'] }
});

// Server Actionでキャッシュを無効化
'use server';
import { revalidateTag } from 'next/cache';

export async function updateProduct() {
  await db.product.update(/* ... */);
  revalidateTag('products'); // 'products'タグのキャッシュのみ削除
}

ポイント

  • 開発モードの罠: next devではキャッシュが正しく動作しません。必ずnext build && next startでテストしてください
  • マイグレーションのコツ: 14から15へアップグレードする際、既存のfetchにcache: 'force-cache'を一括追加すれば、以前の動作を維持できます
  • use cacheディレクティブ: unstable_cacheは非推奨になる予定で、新しいuse cacheディレクティブへの移行が進んでいます。新規プロジェクトではこちらに注目してください