データのフェッチ方法とストリーミング
このページでは、サーバーコンポーネントとクライアントコンポーネントでのデータのフェッチ方法について説明します。また、データに依存するコンテンツのストリーミング方法についても説明します。
データのフェッチ
サーバーコンポーネント
サーバーコンポーネントでデータをフェッチするには、以下の方法があります:
fetch
APIを使用する- ORMまたはデータベースを使用する
fetch
APIを使用する場合
fetch
APIでデータをフェッチするには、コンポーネントを非同期関数に変換し、fetch
呼び出しを待機します。例:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
ORMまたはデータベースを使用する場合
ORMまたはデータベースを使用してデータをフェッチするには、コンポーネントを非同期関数に変換し、呼び出しを待機します:
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
クライアントコンポーネント
クライアントコンポーネントでデータをフェッチするには、次の2つの方法があります:
- Reactの
use
フックを使用する - SWRやReact Queryなどのコミュニティライブラリを使用する
use
フックを使用する場合
Reactのuse
フックを使用して、サーバーからクライアントへデータをストリーミングできます。まず、サーバーコンポーネントでデータをフェッチし、そのプロミスをクライアントコンポーネントにプロップとして渡します:
import Posts from '@/app/ui/posts
import { Suspense } from 'react'
export default function Page() {
// データフェッチ関数をawaitしない
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
次に、クライアントコンポーネントでuse
フックを使用してプロミスを読み取ります:
'use client'
import { use } from 'react'
export default function Posts({
posts,
}: {
posts: Promise<{ id: string; title: string }[]>
}) {
const allPosts = use(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
上記の例では、<Posts />
コンポーネントを<Suspense>
バウンダリでラップする必要があります。これにより、プロミスが解決されている間はフォールバックが表示されます。ストリーミングについて詳しく学びましょう。
コミュニティライブラリを使用する場合
SWRやReact Queryなどのコミュニティライブラリを使用して、クライアントコンポーネントでデータをフェッチできます。これらのライブラリには、キャッシュ、ストリーミングなどの機能に関する独自のセマンティクスがあります。例えば、SWRを使用する場合:
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
ストリーミング
注意: 以下の内容は、アプリケーションで
dynamicIO
設定オプションが有効になっていることを前提としています。このフラグはNext.js 15 canaryで導入されました。
サーバーコンポーネントでasync/await
を使用すると、Next.jsは動的レンダリングを選択します。これはデータが各ユーザーリクエストに対してサーバー上でフェッチされ、レンダリングされることを意味します。データリクエストが遅い場合、ルート全体がレンダリングをブロックされます。
初期ロード時間とユーザーエクスペリエンスを向上させるために、ストリーミングを使用してページのHTMLを小さなチャンクに分割し、それらのチャンクをサーバーからクライアントに段階的に送信できます。
アプリケーションでストリーミングを実装するには、2つの方法があります:
loading.js
ファイルを使用する- Reactの
<Suspense>
コンポーネントを使用する
loading.js
を使用する場合
データがフェッチされている間にページ全体をストリーミングするには、ページと同じフォルダにloading.js
ファイルを作成できます。例えば、app/blog/page.js
をストリーミングするには、app/blog
フォルダ内にファイルを追加します。
export default function Loading() {
// ここでローディングUIを定義します
return <div>Loading...</div>
}
ナビゲーション時、ユーザーはページがレンダリングされている間、レイアウトとローディング状態をすぐに見ることができます。レンダリングが完了すると、新しいコンテンツが自動的に置き換えられます。
裏側では、loading.js
はlayout.js
の中にネストされ、page.js
ファイルとその下の子要素を自動的に<Suspense>
バウンダリでラップします。
このアプローチはルートセグメント(レイアウトとページ)に適していますが、より細かいストリーミングには<Suspense>
を使用できます。
<Suspense>
を使用する場合
<Suspense>
を使用すると、ページのどの部分をストリーミングするかをより細かく制御できます。例えば、<Suspense>
バウンダリの外側にあるページコンテンツをすぐに表示し、バウンダリ内のブログ投稿リストをストリーミングできます。
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* このコンテンツはすぐにクライアントに送信されます */}
<header>
<h1>ブログへようこそ</h1>
<p>以下で最新の投稿をお読みください。</p>
</header>
<main>
{/* <Suspense>バウンダリでラップされたコンテンツはストリーミングされます */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
意味のあるローディング状態を作成する
即時ローディング状態とは、ナビゲーション後すぐにユーザーに表示されるフォールバックUIです。最高のユーザーエクスペリエンスを提供するために、アプリが応答していることをユーザーが理解できるような意味のあるローディング状態をデザインすることをお勧めします。例えば、スケルトンやスピナー、または将来の画面の小さいながらも意味のある部分(カバー写真、タイトルなど)を使用できます。
開発中は、React Devtoolsを使用してコンポーネントのローディング状態をプレビューおよび検査できます。