データ取得、キャッシュ、再検証

データ取得はあらゆるアプリケーションのコア機能です。このページでは、ReactとNext.jsでデータを取得、キャッシュ、再検証する方法を説明します。

データ取得には4つの方法があります:

  1. サーバーサイドでの fetch 使用
  2. サーバーサイドでのサードパーティライブラリ使用
  3. クライアントサイドでのRoute Handler経由
  4. クライアントサイドでのサードパーティライブラリ使用

サーバーサイドでの fetch を使用したデータ取得

Next.jsはネイティブのfetch Web APIを拡張し、サーバーサイドでの各fetchリクエストに対してキャッシュ再検証の動作を設定可能にしています。Reactはfetchを拡張し、Reactコンポーネントツリーのレンダリング中にfetchリクエストを自動的にメモ化します。

fetchはServer Components、Route HandlersServer Actionsasync/awaitと共に使用できます。

例:

async function getData() {
  const res = await fetch('https://api.example.com/...')
  // 戻り値はシリアライズされません
  // Date、Map、Setなどを返せます

  if (!res.ok) {
    // 最も近い`error.js`エラーバウンダリを起動します
    throw new Error('データの取得に失敗しました')
  }

  return res.json()
}

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

  return <main></main>
}
async function getData() {
  const res = await fetch('https://api.example.com/...')
  // 戻り値はシリアライズされません
  // Date、Map、Setなどを返せます

  if (!res.ok) {
    // 最も近い`error.js`エラーバウンダリを起動します
    throw new Error('データの取得に失敗しました')
  }

  return res.json()
}

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

  return <main></main>
}

知っておくと良いこと:

  • Next.jsはServer Componentsでデータを取得する際に便利なcookiesheadersといった関数を提供しています。これらはリクエスト時の情報に依存するため、ルートを動的にレンダリングさせます。
  • Route Handlersでは、fetchリクエストはメモ化されません。Route HandlersはReactコンポーネントツリーの一部ではないためです。
  • Server Actionsでは、fetchリクエストはキャッシュされません(デフォルトはcache: no-store)。
  • TypeScriptでServer Components内でasync/awaitを使用するには、TypeScript 5.1.3以上と@types/react 18.2.8以上が必要です。

データのキャッシュ

キャッシュはデータを保存し、リクエストごとにデータソースから再取得する必要をなくします。

デフォルトでは、Next.jsはサーバー上のData Cachefetchの戻り値を自動的にキャッシュします。これはデータがビルド時またはリクエスト時に取得され、キャッシュされ、各データリクエストで再利用されることを意味します。

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

ただし例外もあり、fetchリクエストは以下の場合キャッシュされません:

Data Cacheとは?

Data Cacheは永続的なHTTPキャッシュです。プラットフォームに応じて、キャッシュは自動的にスケールし、複数リージョン間で共有可能です。

Data Cacheについて詳しく学びます。

データの再検証

再検証はData Cacheをクリアし、最新のデータを再取得するプロセスです。データが変更され、最新情報を表示したい場合に便利です。

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

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

時間ベースの再検証

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

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

または、ルートセグメント内のすべてのfetchリクエストを再検証するには、Segment Config Optionsを使用できます。

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

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

時間ベースの再検証について詳しく学びます。

オンデマンド再検証

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

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

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

例えば、以下のfetchリクエストはキャッシュタグcollectionを追加します:

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

その後、Server ActionでrevalidateTagを呼び出してcollectionタグが付けられたこのfetch呼び出しを再検証できます:

'use server'

import { revalidateTag } from 'next/cache'

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

import { revalidateTag } from 'next/cache'

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

オンデマンド再検証について詳しく学びます。

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

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

データキャッシュのオプトアウト

fetchリクエストは以下の場合キャッシュされません

  • fetchリクエストにcache: 'no-store'が追加されている場合
  • 個々のfetchリクエストにrevalidate: 0オプションが追加されている場合
  • POSTメソッドを使用するRouter Handler内でfetchリクエストが行われた場合
  • headersまたはcookies使用後にfetchリクエストが行われた場合
  • const dynamic = 'force-dynamic'ルートセグメントオプションが使用されている場合
  • fetchCacheルートセグメントオプションがデフォルトでキャッシュをスキップするように設定されている場合
  • fetchリクエストがAuthorizationまたはCookieヘッダーを使用し、コンポーネントツリー内にその上の未キャッシュリクエストがある場合

個々のfetchリクエスト

個々のfetchリクエストでキャッシュをオプトアウトするには、fetchcacheオプションを'no-store'に設定します。これにより、リクエストごとに動的にデータが取得されます。

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

利用可能なすべてのcacheオプションはfetch APIリファレンスで確認できます。

複数のfetchリクエスト

ルートセグメント(例:LayoutやPage)に複数のfetchリクエストがある場合、Segment Config Optionsを使用してセグメント内のすべてのデータリクエストのキャッシュ動作を設定できます。

ただし、各fetchリクエストのキャッシュ動作を個別に設定することを推奨します。これにより、キャッシュ動作をより細かく制御できます。

サーバーサイドでのサードパーティライブラリを使用したデータ取得

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

データがキャッシュされるかどうかは、ルートセグメントが静的または動的にレンダリングされるかによります。セグメントが静的(デフォルト)の場合、リクエストの出力はルートセグメントの一部としてキャッシュされ、再検証されます。セグメントが動的な場合、リクエストの出力はキャッシュされず、セグメントがレンダリングされるたびにリクエストごとに再取得されます。

実験的なunstable_cache APIも使用できます。

以下の例では:

  • Reactのcache関数を使用してデータリクエストをメモ化しています。
  • LayoutとPageセグメントでrevalidateオプションを3600に設定し、データが最大1時間ごとにキャッシュおよび再検証されます。
import { cache } from 'react'

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

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

getItem関数が2回呼び出されても、データベースへのクエリは1回だけ実行されます。

import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // データを最大1時間ごとに再検証

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

export const revalidate = 3600 // データを最大1時間ごとに再検証

export default async function Layout({ params: { id } }) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // データを最大1時間ごとに再検証

export default async function Page({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // データを最大1時間ごとに再検証

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

クライアントサイドでのRoute Handlersを使用したデータ取得

クライアントコンポーネントでデータを取得する必要がある場合、クライアントからRoute Handlerを呼び出せます。Route Handlersはサーバー上で実行され、データをクライアントに返します。APIトークンなどの機密情報をクライアントに公開したくない場合に有用です。

例についてはRoute Handlerドキュメントを参照してください。

Server ComponentsとRoute Handlers

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

クライアントサイドでのサードパーティライブラリを使用したデータ取得

SWRTanStack Queryなどのサードパーティライブラリを使用してクライアントサイドでデータを取得することもできます。これらのライブラリは、リクエストのメモ化、キャッシュ、再検証、データ変更のための独自のAPIを提供します。

将来のAPI:

use関数から返されたPromiseを受け入れ処理するReact関数です。現在、クライアントコンポーネントでfetchuseでラップすることは推奨されず、複数の再レンダリングを引き起こす可能性があります。useについてReactドキュメントで詳しく学びます。