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

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

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

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

サーバーサイドでのfetchによるデータフェッチング

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

Server Componentsasync/awaitRoute HandlersServer Actionsfetchを使用できます。

例:

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では、フェッチリクエストはメモ化されません。Route HandlersはReactコンポーネントツリーの一部ではないためです。
  • TypeScriptでServer Component内でasync/awaitを使用するには、TypeScript 5.1.3以上と@types/react 18.2.8以上が必要です。

データのキャッシュ

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

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

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

POSTメソッドを使用するfetchリクエストも自動的にキャッシュされます。Route Handler内でPOSTメソッドを使用する場合を除き、その場合はキャッシュされません。

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リクエストが独立して再検証されます。

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

オンデマンド再検証

データは、Route HandlerまたはServer Action内でパス(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()
  // ...
}

Route Handlerを使用する場合、Next.jsアプリのみが知る秘密トークンを作成する必要があります。この秘密は不正な再検証試行を防ぐために使用されます。例えば、次のURL構造でルートにアクセスできます(手動またはWebhookで):

URL
https://<your-site.com>/api/revalidate?tag=collection&secret=<token>
import { NextRequest } from 'next/server'
import { revalidateTag } from 'next/cache'

// 例:`your-website.com/api/revalidate?tag=collection&secret=<token>`へのWebhook
export async function POST(request: NextRequest) {
  const secret = request.nextUrl.searchParams.get('secret')
  const tag = request.nextUrl.searchParams.get('tag')

  if (secret !== process.env.MY_SECRET_TOKEN) {
    return Response.json({ message: '無効な秘密トークン' }, { status: 401 })
  }

  if (!tag) {
    return Response.json({ message: 'タグパラメータが不足しています' }, { status: 400 })
  }

  revalidateTag(tag)

  return Response.json({ revalidated: true, now: Date.now() })
}
import { revalidateTag } from 'next/cache'

// 例:`your-website.com/api/revalidate?tag=collection&secret=<token>`へのWebhook
export async function POST(request) {
  const secret = request.nextUrl.searchParams.get('secret')
  const tag = request.nextUrl.searchParams.get('tag')

  if (secret !== process.env.MY_SECRET_TOKEN) {
    return Response.json({ message: '無効な秘密トークン' }, { status: 401 })
  }

  if (!tag) {
    return Response.json({ message: 'タグパラメータが不足しています' }, { status: 400 })
  }

  revalidateTag(tag)

  return Response.json({ revalidated: true, now: Date.now() })
}

または、revalidatePathを使用して、パスに関連付けられたすべてのデータを再検証できます。

import { NextRequest } from 'next/server'
import { revalidatePath } from 'next/cache'

export async function POST(request: NextRequest) {
  const path = request.nextUrl.searchParams.get('path')

  if (!path) {
    return Response.json({ message: 'パスパラメータが不足しています' }, { status: 400 })
  }

  revalidatePath(path)

  return Response.json({ revalidated: true, now: Date.now() })
}
import { revalidatePath } from 'next/cache'

export async function POST(request) {
  const path = request.nextUrl.searchParams.get('path')

  if (!path) {
    return Response.json({ message: 'パスパラメータが不足しています' }, { status: 400 })
  }

  revalidatePath(path)

  return Response.json({ revalidated: true, now: Date.now() })
}

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

エラー処理と再検証

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

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

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

  • fetchリクエストにcache: 'no-store'が追加されている場合
  • 個々のfetchリクエストにrevalidate: 0オプションが追加されている場合
  • fetchリクエストがPOSTメソッドを使用するRouter Handler内にある場合
  • fetchリクエストがheadersまたはcookiesの使用後に来る場合
  • 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リクエスト

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

例えば、const dynamic = 'force-dynamic'を使用すると、すべてのデータがリクエスト時にフェッチされ、セグメントが動的にレンダリングされます。

layout.js | page.js
// 追加
export const dynamic = 'force-dynamic'

ルートセグメントの静的および動的動作を細かく制御するための多くのSegment Configオプションがあります。詳細はAPIリファレンスを参照してください。

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

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

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

知っておくと良いこと:

Next.jsは、個々のサードパーティリクエストのキャッシュと再検証の動作を設定するためのAPI、unstable_cacheを開発中です。

以下の例では:

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

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

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

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

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 default async function Layout({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

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

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

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内で直接データをフェッチできます。

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

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

将来のAPI:

useは、関数によって返されるPromiseを受け入れ、処理するReact関数です。現在、Client Componentsでfetchuseでラップすることは推奨されていません。複数の再レンダリングを引き起こす可能性があります。useについて詳しくはReact RFCを参照してください。