メインコンテンツまでスキップ

データのフェッチ、キャッシュ、再検証

データ取得は、あらゆるアプリケーションの中核をなす部分です。このページでは、React と Next.js でデータをフェッチ、キャッシュ、再検証する方法について説明します。

データをフェッチする方法は 4 つあります:

  1. fetchを使用したサーバー上でのデータフェッチ
  2. サードパーティライブラリを使用したサーバー上でのデータフェッチ
  3. ルートハンドラを介したクライアント上でのデータフェッチ
  4. サードパーティライブラリを使用したクライアント上でのデータフェッチ

fetchを使用したサーバー上でのデータフェッチ

Next.js はネイティブのfetch Web APIを拡張し、サーバー上の各 fetch リクエストに対するキャッシュ再検証の動作を設定できるようになりました。React はfetchを拡張して、React コンポーネントツリーのレンダリング中にフェッチリクエストを自動的にメモします。

async/awaitを使用した Server Components、Route ハンドラおよびServer Actionsfetchを使用できます。

例:

app/page.tsx
async function getData() {
const res = await fetch('https://api.example.com/...')
// 返り値はシリアライズされない
// Date, Map, Setを返すことができる

if (!res.ok) {
// 最も近い`error.js`エラーバウンダリが有効になる。
throw new Error('Failed to fetch data')
}

return res.json()
}

export default async function Page() {
const data = await getData()

return <main></main>
}

Good to know:

  • Next.js は、cookiesheadersなど、サーバーコンポーネントのデータを取得する際に必要となる便利な関数を提供します。これらはリクエスト時刻の情報に依存するため、ルートが動的にレンダリングされます
  • Route ハンドラーは React コンポーネントツリーの一部ではないため、Route ハンドラーではfetchリクエストはメモされません
  • TypeScript の Server Component でasync/awaitを使用するには、TypeScript 5.1.3以上と @types/react 18.2.8 以上が必要です

データのキャッシュ

キャッシュはデータを保存するので、リクエストのたびにデータソースから再フェッチする必要はありません。

デフォルトでは、Next.js はfetchで返された値をサーバー上のデータ・キャッシュに自動的にキャッシュします。つまり、データはビルド時またはリクエスト時にfetchされ、キャッシュされて各データリクエストで再利用されます。

// 'force-cache'がデフォルトなので省略可能
fetch('https://...', { cache: 'force-cache' })

POSTメソッドを使用するfetchリクエストも自動的にキャッシュされます。POSTメソッドを使うRoute ハンドラの内部でない限り、キャッシュされません。

データ・キャッシュとは何ですか?

データ・キャッシュは永続的なHTTP キャッシュです。プラットフォームによっては、キャッシュを自動的に拡張し、複数のリージョンで共有することができます。

データ・キャッシュの詳細については、こちらを参照してください。

データの再検証

再検証とは、データキャッシュを削除し、最新のデータを再取得するプロセスです。これは、データが変更され、最新の情報を確実に表示したい場合に使用します。

キャッシュされたデータは、2 つの方法で再検証できます:

  • 時間ベースの再検証:一定時間が経過したデータを自動的に再検証します。これは、変更頻度が低く、鮮度がそれほど重要でないデータに有効です
  • オンデマンドの再検証:イベント(フォーム送信など)に基づいてデータを手動で再検証します。オンデマンド再検証では、タグベースまたはパスベースのアプローチを使用して、データのグループを一度に再検証できます。これは、最新のデータをできるだけ早く表示したい場合に便利です(ヘッドレス CMS のコンテンツが更新された場合など)

時間ベースの再検証

時間間隔でデータを再検証するには、fetchnext.revalidateオプションを使用して、リソースのキャッシュ有効期間(秒)を設定します。

fetch('https://...', { next: { revalidate: 3600 } })

あるいは、ルート Segment 内のすべてのfetchリクエストを再検証するには、セグメント設定オプションを使用します。

layout.js / page.js
export const revalidate = 3600 // 1時間ごとに再検証

静的にレンダリングされたルートに複数のfetchリクエストがあり、それぞれに異なる再検証頻度が設定されている場合。もっとも低い時間がすべてのリクエストに使用されます。動的にレンダリングされるルートの場合、各fetchリクエストは個別に再検証されます。

時間ベースの再検証の詳細については、こちらを参照してください。

オンデマンドの再検証

データは、Route Handler または Server Actions 内のパス(revalidatePath)またはキャッシュタグ(revalidateTag)によってオンデマンドで再検証できます。

Next.js には、ルート間のfetchリクエストを無効にするためのキャッシュタグシステムがあります。

  1. fetchを使用する場合、キャッシュエントリに 1 つ以上のタグを付けるオプションがあります
  2. その後、revalidateTagを呼び出して、そのタグに関連付けられたすべてのエントリを再検証できます

たとえば、次のfetchリクエストはキャッシュ・タグ collectionを追加します:

app/page.tsx
export default async function Page() {
const res = await fetch('https://...', { next: { tags: ['collection'] } })
const data = await res.json()
// ...
}

その後、Server Action で revalidateTag を呼び出すことで、collection でタグ付けされたこのフェッチ・コールを再検証できます:

app/actions.ts
'use server'

import { revalidateTag } from 'next/cache'

export default async function action() {
revalidateTag('collection')
}

オンデマンドの再検証の詳細については、こちらを参照してください。

エラーハンドリングと再検証

データの再検証を試みてエラーがスローされた場合、最後に正常に生成されたデータがキャッシュから引き続き提供されます。次のリクエストで、Next.js はデータの再検証を再試行します。

データのキャッシュを停止する

fetchリクエストは以下のケースではキャッシュされません:

  • cache: 'no-storefetchリクエストに指定されている
  • revalidate: 0 オプションが個々のfetchリクエストに指定されている
  • fetchリクエストがルートハンドラのPOSTメソッドで使用されている
  • fetchリクエストがheaderまたはcookiesの後で使用されている
  • ルート Segment でconst dynamic = 'force-dynamic'が指定されている
  • ルート Segment のfetchCacheオプションがデフォルトでキャッシュをしないように指定されている
  • fetchリクエストでAuthorizationまたはCookieヘッダーが指定されていて、コンポーネントツリー上にキャッシュされていないリクエストがある

個々のfetchリクエスト

個々のfetchリクエストに対してキャッシュを行わないようにするには、fetchのキャッシュ・オプションを'no-store'に設定します。これにより、リクエストごとに動的にデータがフェッチされます。

layout.js | page.js
fetch('https://...', { cache: 'no-store' })

利用可能なすべてのcacheオプションは、 fetch API リファレンスを参照してください。

複数のfetchリクエスト

ルート Segment(Layout や Page など)に複数のfetchリクエストがある場合、Segment 設定オプションを使用して Segment 内のすべてのリクエストのキャッシュ動作を設定できます。

しかし、各取り込みリクエストのキャッシュ動作を個別に設定することをお勧めします。これにより、キャッシュ動作をより細かく制御することができます。

サードパーティライブラリを使用したサーバー上でのデータフェッチ

fetchをサポートまたは公開しないサードパーティライブラリ(データベース、CMS、ORM クライアントなど)を使用している場合は、ルート Segment の設定オプションと React のcache関数を使用して、これらのリクエストのキャッシュと再検証の動作を設定できます。

データがキャッシュされるかどうかは、ルート Segment が静的にレンダリングされるか動的にレンダリングされるかに依存します。Segment が静的な場合(デフォルト)、リクエストの出力はキャッシュされ、ルート Segment の一部として再検証されます。Segment が動的な場合、リクエストの出力はキャッシュされず、Segment がレンダリングされる際、リクエストごとに再フェッチされます。

Experience 版のunstable_cache APIを使うこともできます。

下の例では

  • React のcache関数は、データのリクエストをメモするために使用されます
  • revalidateオプションは layout.tspage.ts のセグメントで3600に設定されています。つまり、データはキャッシュされ、最大で 1 時間ごとに再検証されます
app/utils.ts
import { cache } from 'react'

export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})

getItem関数は 2 回呼び出されますが、データベースへの問い合わせは 1 回だけです。

app/item/[id]/layout.tsx
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // revalidate the data at most every hour

export default async function Layout({
params: { id },
}: {
params: { id: string }
}) {
const item = await getItem(id)
// ...
}
app/item/[id]/page.tsx
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // revalidate the data at most every hour

export default async function Page({
params: { id },
}: {
params: { id: string }
}) {
const item = await getItem(id)
// ...
}

ルートハンドラを介したクライアント上でのデータフェッチ

Client Components でデータを取得する必要がある場合、クライアントからルートハンドラを呼び出すことができます。ルートハンドラはサーバー上で実行され、クライアントにデータを返します。これは、API トークンのような機密情報をクライアントへ公開したくない場合に便利です。

例についてはルート・ハンドラのドキュメントを参照してください。

Server Components とルートハンドラ

Server Components はサーバー上でレンダリングされるため、データを取得するために Server Component からルートハンドラを呼び出す必要はありません。代わりに、Server Component 内で直接データを取得できます。

サードパーティライブラリを使用したクライアント上でのデータフェッチ

SWRReact Queryのようなサードパーティ製のライブラリを使用して、クライアント上でデータをフェッチできます。これらのライブラリはリクエストをメモしたり、キャッシュしたり、再検証したり、データを変異させたりするための独自の API を提供しています。

将来の API

useは React の関数で、関数から返されたプロミスを受け取って処理します。fetchuseでラップすることは、現在のところ Client Components では推奨されていません。useの詳細についてはReact RFCを参照してください。