データフェッチング、キャッシュ、再検証
データフェッチングはあらゆるアプリケーションのコアとなる部分です。このページでは、ReactとNext.jsでデータをフェッチ、キャッシュ、再検証する方法について説明します。
データをフェッチする方法は4つあります:
サーバーサイドでのfetch
によるデータフェッチング
Next.jsはネイティブのfetch
Web APIを拡張し、サーバー上の各フェッチリクエストに対してキャッシュと再検証の動作を設定できるようにしています。Reactはfetch
を拡張して、Reactコンポーネントツリーのレンダリング中にフェッチリクエストを自動的にメモ化します。
Server Componentsのasync
/await
、Route Handlers、Server Actionsでfetch
を使用できます。
例:
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はサーバー上のData Cacheにfetch
の戻り値を自動的にキャッシュします。これは、データがビルド時またはリクエスト時にフェッチされ、キャッシュされ、各データリクエストで再利用されることを意味します。
// 'force-cache'はデフォルトで、省略可能です
fetch('https://...', { cache: 'force-cache' })
POST
メソッドを使用するfetch
リクエストも自動的にキャッシュされます。Route Handler内でPOST
メソッドを使用する場合を除き、その場合はキャッシュされません。
Data Cacheとは?
Data Cacheは永続的なHTTPキャッシュです。プラットフォームによっては、キャッシュは自動的にスケーリングされ、複数のリージョン間で共有できます。
Data Cacheについて詳しく学びます。
データの再検証
再検証はData Cacheをクリアし、最新のデータを再フェッチするプロセスです。データが変更され、最新の情報を表示したい場合に便利です。
キャッシュされたデータは2つの方法で再検証できます:
- 時間ベースの再検証:一定時間が経過した後、自動的にデータを再検証します。頻繁に変更されず、新鮮さがそれほど重要でないデータに便利です。
- オンデマンド再検証:イベント(フォーム送信など)に基づいて手動でデータを再検証します。オンデマンド再検証では、タグベースまたはパスベースのアプローチを使用して、一度にデータのグループを再検証できます。ヘッドレスCMSからのコンテンツが更新されたときなど、できるだけ早く最新のデータを表示したい場合に便利です。
時間ベースの再検証
一定間隔でデータを再検証するには、fetch
のnext.revalidate
オプションを使用してリソースのキャッシュ寿命(秒単位)を設定します。
fetch('https://...', { next: { revalidate: 3600 } })
または、ルートセグメント内のすべてのfetch
リクエストを再検証するには、Segment Config Optionsを使用できます。
export const revalidate = 3600 // 最大1時間ごとに再検証
静的にレンダリングされるルートに複数のフェッチリクエストがあり、それぞれに異なる再検証頻度がある場合、すべてのリクエストに対して最も短い時間が使用されます。動的にレンダリングされるルートでは、各fetch
リクエストが独立して再検証されます。
時間ベースの再検証について詳しく学びます。
オンデマンド再検証
データは、Route HandlerまたはServer Action内でパス(revalidatePath
)またはキャッシュタグ(revalidateTag
)によってオンデマンドで再検証できます。
Next.jsには、ルート間でfetch
リクエストを無効化するキャッシュタグシステムがあります。
fetch
を使用する際、1つ以上のタグでキャッシュエントリをタグ付けできます。- その後、
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で):
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
リクエストのキャッシュをオプトアウトするには、fetch
のcache
オプションを'no-store'
に設定します。これにより、リクエストごとに動的にデータがフェッチされます。
fetch('https://...', { cache: 'no-store' })
利用可能なすべてのcache
オプションはfetch
APIリファレンスで確認できます。
複数のfetch
リクエスト
ルートセグメント(レイアウトやページなど)に複数のfetch
リクエストがある場合、Segment Config Optionsを使用してセグメント内のすべてのデータリクエストのキャッシュ動作を設定できます。
例えば、const dynamic = 'force-dynamic'
を使用すると、すべてのデータがリクエスト時にフェッチされ、セグメントが動的にレンダリングされます。
// 追加
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内で直接データをフェッチできます。
サードパーティライブラリを使用したクライアントサイドでのデータフェッチング
SWRやReact Queryなどのサードパーティライブラリを使用して、クライアントでデータをフェッチすることもできます。これらのライブラリは、リクエストのメモ化、キャッシュ、再検証、データの変更のための独自のAPIを提供します。
将来のAPI:
use
は、関数によって返されるPromiseを受け入れ、処理するReact関数です。現在、Client Componentsでfetch
をuse
でラップすることは推奨されていません。複数の再レンダリングを引き起こす可能性があります。use
について詳しくはReact RFCを参照してください。