🙏

Next.js 15のfetchでのキャッシュの扱い方

に公開

Next.jsはReactベースのフレームワークとして、SSG、SSR、ISRなど多彩なレンダリング方式を提供しています。
本記事では、Next.js15でのfetchによるデータ取得時のキャッシュの設定方法について紹介します。

1. Next.js15でのfetchとキャッシュの関係

Next.js15では、サーバーコンポーネントやクライアントコンポーネントからfetchを利用する際に、キャッシュを細かく制御でき、キャッシュについては、大きく4種類あります。

  • Request Memoization
  • Data Cache
  • Full Route Cache
  • Router Cache

詳しい説明は省きますが、下記の記事がわかりやすいです。

2. 基本的なfetchキャッシュ設定

Next.js 15では、サーバーコンポーネント内のfetchは、Data Cacheはデフォルトで無効化されています。そのため、毎回オリジンにリクエストされます。
今回は、オプションで、cache: 'force-cache' を使用した例を示します。

// app/posts/page.tsx
export const revalidate = 60; //60秒ごとに更新

export default async function PostsPage() {
  const res = await fetch("https://api.example.com/posts", {
    next: { revalidate: 60 }, // データキャッシュの再検証期間を設定
    cache: "force-cache",  // キャッシュを優先的に利用し、必要に応じて更新
  });

  const posts = await res.json();

  return (
    <div>
      {posts.map((post: any) => (
        <h2 key={post.id}>{post.title}</h2>
      ))}
    </div>
  );
}

cache: "force-cache"を設定すると、キャッシュが存在し、かつ有効期限内である場合にキャッシュから返されます。存在しない場合にのみフェッチします。
上記のキャッシュの再検証には2種類あります。
1つ目は、Time-based Revalidationで、俗に言うISRを指し、一定の時間が経過後に、新規リクエストが行われた後に、データを再検証します。
next: { revalidate: 60 } のような形で指定できます。

そしてOn-demand Revaldationとして、revalidatePath()やrevalidateTag()による再検証があります。
上記は、フォーム送信などのイベントに基づいて行われ、指定したパスもしくはタグに関連するキャッシュエントリをすべて削除します。
サーバーアクションでのフォームなどと相性が良いです。

3. クライアント側のfetch

クライアントでもfetchのキャッシュを設定できますが、サーバーコンポーネントのデータキャッシュとは異なるため、クライアント側で状態管理や再検証を行う必要があるため、ブラウザ側のキャッシュやSWR/React Queryなどサードパーティを使うのがおすすめです。
SWRだとコードの記述量が減り、エラーハンドリング・ローディング表示が楽です。

"use client"

import useSWR from "swr"

const fetcher = (url: string) =>
  fetch(url, { cache: "no-store" }).then((res) => {
    if (!res.ok) throw new Error("Failed to fetch data")
    return res.json()
  })

export default function PostsClient() {
  const { data: posts, error, isLoading } = useSWR("/api/posts", fetcher, {
    revalidateOnFocus: true, // タブ復帰時に再フェッチ
  })

  if (isLoading) return <p>読み込み中です...</p>
  if (error) return <p>データの取得に失敗しました。</p>

  return (
    <div>
      {posts?.length ? (
        posts.map((post: any) => <h2 key={post.id}>{post.title}</h2>)
      ) : (
        <p>投稿がありません。</p>
      )}
    </div>
  )
}

4. まとめ

Next.js15のfetchでのキャッシュを理解すると、ページの特性に合わせて、キャッシュを設定することができます。

  • 更新が少ない場合 → force-cacheを利用
  • 定期的に更新がある場合 → ISR(Time-based Revalidation)
  • 更新が多い場合 → no-storeを利用, On-demand Revaldationを利用

そして、dynamicの値を設定すれば、レンダリング形式(SSR, SSGなど)の設定が可能なため、
ページに合わせて最適なレンダリング形式も設定しましょう。
layout.tsx | page.tsx | route.ts で設定可能です。

export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'

https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config

ただし最近では、Next.jsのキャッシュを正確に理解するのが難しいという意見を受けて、細かくカスタマイズできるuse cacheディレクティブが、Next.js16から正式導入されました。
今後は下記の方が今後使われていくと思います。
https://nextjsjp.org/docs/app/api-reference/directives/use-cache

ですが、上記無しでも最低限のキャッシュ管理はできるので、慣れていない人はぜひ試してみてください。

引用
https://nextjs.org/docs/15/app/api-reference/functions/fetch
https://nextjs.org/docs/app/guides/caching

Discussion