データの取得とストリーミング方法
このページでは、server componentとclient componentでデータを取得する方法、およびデータに依存するコンテンツをストリーミングする方法について説明します。
データの取得
Server Components
Server Componentsでデータを取得するには、以下の方法があります:
fetchAPIを使用する- ORMやデータベースを使用する
fetch APIを使用する
fetch APIを使用してデータを取得するには、コンポーネントを非同期関数に変換し、fetch呼び出しを待機します。例えば:
- TypeScript
- JavaScript
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>
)
}
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やデータベースを使用する
Server Componentsはサーバー上でレンダリングされるため、ORMやデータベースクライアントを使用して安全にデータベースクエリを実行できます。コンポーネントを非同期関数に変換し、呼び出しを待機します:
- TypeScript
- JavaScript
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>
)
}
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>
)
}
Client Components
Client Componentsでデータを取得する方法は2つあります:
- Reactの
useフックを使用する - SWRやReact Queryのようなコミュニティライブラリを使用する
useフックを使用する
Reactのuseフックを使用して、サーバーからクライアントにデータをストリーミングできます。まず、Server componentでデータを取得し、promiseをpropとしてClient Componentに渡します:
- TypeScript
- JavaScript
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
export default function Page() {
// データ取得関数を待機しない
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
export default function Page() {
// データ取得関数を待機しない
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
次に、Client Componentでuseフックを使用してpromiseを読み取ります:
- TypeScript
- JavaScript
'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>
)
}
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const posts = use(posts)
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
上記の例では、<Posts />コンポーネントを<Suspense> boundaryでラップする必要があります。これは、promiseが解決される間、フォールバックが表示されることを意味します。ストリーミングについて詳しく学びましょう。
コミュニティライブラリ
SWRやReact Queryのようなコミュニティライブラリを使用して、Client Componentsでデータを取得できます。これらのライブラリは、キャッシング、ストリーミング、その他の機能に独自のセマンティクスを持っています。例えば、SWRを使用する場合:
- TypeScript
- JavaScript
'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>
)
}
'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) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
ストリーミング
警告: 以下の内容は、アプリケーションで
dynamicIO設定オプションが有効になっていることを前提としています。このフラグはNext.js 15 canaryで導入されました。
Server Componentsでasync/awaitを使用する場合、Next.jsは動的レンダリングを選択します。これは、データがユーザーのリクエストごとにサーバーで取得され、レンダリングされることを意味します。データリクエストが遅い場合、ルート全体がレンダリングをブロックされます。
初期ロード時間とユーザーエクスペリエンスを向上させるために、ページのHTMLを小さなチャンクに分割し、それらのチャンクをサーバーからクライアントに段階的に送信するストリーミングを使用できます。

アプリケーションでストリーミングを実装する方法は2つあります:
loading.jsファイルを使用する- Reactの
<Suspense>コンポーネントを使用する
loading.jsを使用する
ページと同じフォルダにloading.jsファイルを作成して、データが取得されている間にページ全体をストリーミングできます。例えば、app/blog/page.jsをストリーミングするには、app/blogフォルダ内にファイルを追加します。

- TypeScript
- JavaScript
export default function Loading() {
// ここでLoading UIを定義
return <div>Loading...</div>
}
export default function Loading() {
// ここでLoading UIを定義
return <div>Loading...</div>
}
ナビゲーション時に、ユーザーはページがレンダリングされている間、レイアウトとローディング状態をすぐに見ることができます。レンダリングが完了すると、新しいコンテンツが自動的に入れ替わります。

裏側では、loading.jsはlayout.js内にネストされ、page.jsファイルとその下のすべての子を自動的に<Suspense> boundaryでラップします。

このアプローチはルートセグメント(レイアウトとページ)に適していますが、より細かいストリーミングには<Suspense>を使用できます。
<Suspense>を使用する
<Suspense>を使用すると、ページのどの部分をストリーミングするかをより細かく制御できます。例えば、<Suspense> boundaryの外にあるページコンテンツをすぐに表示し、boundary内のブログ投稿のリストをストリーミングできます。
- TypeScript
- JavaScript
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* このコンテンツはすぐにクライアントに送信されます */}
<header>
<h1>Welcome to the Blog</h1>
<p>Read the latest posts below.</p>
</header>
<main>
{/* <Suspense> boundaryでラップされたコンテンツはストリーミングされます */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* このコンテンツはすぐにクライアントに送信されます */}
<header>
<h1>Welcome to the Blog</h1>
<p>Read the latest posts below.</p>
</header>
<main>
{/* <Suspense> boundaryでラップされたコンテンツはストリーミングされます */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
意味のあるローディング状態の作成
インスタントローディング状態は、ナビゲーション後にユーザーにすぐに表示されるフォールバックUIです。最良のユーザーエクスペリエンスを提供するために、意味のあるローディング状態を設計し、アプリが応答していることをユーザーに理解させることをお勧めします。例えば、スケルトンやスピナー、またはカバーフォトやタイトルなどの将来の画面の小さくても意味のある部分を使用できます。
開発中は、React Devtoolsを使用してコンポーネントのローディング状態をプレビューおよび検査できます。