データフェッチとキャッシング
このガイドでは、Next.jsにおけるデータフェッチとキャッシングの基本について、実践的な例とベストプラクティスを交えて説明します。
Next.js でのデータフェッチの最小限の例を以下に示します。
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog')
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
この例では、非同期Reactサーバーコンポーネントで`fetch` APIを使用して、基本的なサーバーサイドデータフェッチを実行しています。
リファレンス
fetch
- React `cache`
- Next.js `unstable_cache`
例
`fetch` APIを使用したサーバー上でのデータフェッチ
このコンポーネントは、ブログ投稿のリストをフェッチして表示します。 `fetch` からのレスポンスは自動的にキャッシュされます。
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog')
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
このルートの他の場所で動的APIを使用していない場合、`next build` 中に静的ページにプリレンダリングされます。その後、データは差分静的再生成を使用して更新できます。
`fetch` からのレスポンスをキャッシュしたくない場合は、次の操作を実行できます。
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })
ORMまたはデータベースを使用したサーバー上でのデータフェッチ
このコンポーネントはブログ投稿のリストを取得して表示します。データベースからのレスポンスはデフォルトではキャッシュされませんが、追加設定を行うことでキャッシュできます。
import { db, posts } from '@/lib/db'
export default async function Page() {
let allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
このルートの他の場所で動的APIを使用していない場合、`next build` 中に静的ページにプリレンダリングされます。その後、データは差分静的再生成を使用して更新できます。
ページのプリレンダリングを防ぐには、ファイルに以下を追加します。
export const dynamic = 'force-dynamic'
ただし、一般的には、cookies
、headers
、またはページプロップから受信するsearchParams
の読み取りなどの関数を使用します。これらの関数は、ページを自動的に動的にレンダリングします。この場合、明示的にforce-dynamic
を使用する必要はありません。
クライアント側でのデータフェッチ
最初にサーバー側でデータを取得することをお勧めします。
ただし、クライアント側でのデータフェッチが理にかなっている場合もあります。このようなシナリオでは、useEffect
で手動でfetch
を呼び出す(推奨されません)か、コミュニティで人気のあるReactライブラリ(SWRやReact Queryなど)を利用してクライアントフェッチを行うことができます。
'use client'
import { useState, useEffect } from 'react'
export function Posts() {
const [posts, setPosts] = useState(null)
useEffect(() => {
async function fetchPosts() {
let res = await fetch('https://api.vercel.app/blog')
let data = await res.json()
setPosts(data)
}
fetchPosts()
}, [])
if (!posts) return <div>Loading...</div>
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
ORMまたはデータベースを使用したデータのキャッシュ
unstable_cache
APIを使用してレスポンスをキャッシュし、next build
の実行時にページをプリレンダリングできるようにすることができます。
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const allPosts = await getPosts()
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
この例では、データベースクエリの結果を1時間(3600秒)キャッシュします。また、キャッシュタグposts
を追加します。これは、増分静的再生成を使用して無効化できます。
複数の関数間でのデータの再利用
Next.jsは、generateMetadata
やgenerateStaticParams
などのAPIを使用します。これらのAPIでは、page
でフェッチされた同じデータを使用する必要があります。
fetch
を使用している場合、リクエストは自動的にメモ化されます。つまり、同じURLを同じオプションで安全に呼び出すことができ、リクエストは1回だけ行われます。
import { notFound } from 'next/navigation'
interface Post {
id: string
title: string
content: string
}
async function getPost(id: string) {
let res = await fetch(`https://api.vercel.app/blog/${id}`)
let post: Post = await res.json()
if (!post) notFound()
return post
}
export async function generateStaticParams() {
let posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post: Post) => ({
id: post.id,
}))
}
export async function generateMetadata({ params }: { params: { id: string } }) {
let post = await getPost(params.id)
return {
title: post.title,
}
}
export default async function Page({ params }: { params: { id: string } }) {
let post = await getPost(params.id)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
fetch
を使用せず、代わりにORMまたはデータベースを直接使用している場合は、データフェッチをReactのcache
関数でラップできます。これにより、重複が排除され、クエリは1回だけ実行されます。
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db' // Example with Drizzle ORM
import { notFound } from 'next/navigation'
export const getPost = cache(async (id) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
if (!post) notFound()
return post
})
キャッシュされたデータの再検証
増分静的再生成を使用したキャッシュされたデータの再検証の詳細をご覧ください。
パターン
並列および順次データフェッチ
import Item, { preload, checkIsAvailable } from '@/components/Item'
export default async function Page({
params: { id },
}: {
params: { id: string }
}) {
// starting loading item data
preload(id)
// perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
知っておくと良いこと:「プリロード」関数は、APIではなくパターンであるため、任意の名前を付けることができます。
プリロードパターンでのReact cache
およびserver-only
の使用 utils/get-item.tsimport { cache } from 'react'
import 'server-only'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})
import { cache } from 'react'
import 'server-only'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})