Menu

データのフェッチ方法とストリーミング

このページでは、サーバーコンポーネントクライアントコンポーネントでのデータのフェッチ方法について説明します。また、データに依存するコンテンツのストリーミング方法についても説明します。

データのフェッチ

サーバーコンポーネント

サーバーコンポーネントでデータをフェッチするには、以下の方法があります:

  1. fetch APIを使用する
  2. ORMまたはデータベースを使用する

fetch APIを使用する場合

fetch APIでデータをフェッチするには、コンポーネントを非同期関数に変換し、fetch呼び出しを待機します。例:

app/blog/page.tsx
TypeScript
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またはデータベースを使用してデータをフェッチするには、コンポーネントを非同期関数に変換し、呼び出しを待機します:

app/blog/page.tsx
TypeScript
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つの方法があります:

  1. Reactのuseフックを使用する
  2. SWRReact Queryなどのコミュニティライブラリを使用する

useフックを使用する場合

Reactのuseフックを使用して、サーバーからクライアントへデータをストリーミングできます。まず、サーバーコンポーネントでデータをフェッチし、そのプロミスをクライアントコンポーネントにプロップとして渡します:

app/blog/page.tsx
TypeScript
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フックを使用してプロミスを読み取ります:

app/ui/posts.tsx
TypeScript
'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>バウンダリでラップする必要があります。これにより、プロミスが解決されている間はフォールバックが表示されます。ストリーミングについて詳しく学びましょう。

コミュニティライブラリを使用する場合

SWRReact Queryなどのコミュニティライブラリを使用して、クライアントコンポーネントでデータをフェッチできます。これらのライブラリには、キャッシュ、ストリーミングなどの機能に関する独自のセマンティクスがあります。例えば、SWRを使用する場合:

app/blog/page.tsx
TypeScript
'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つの方法があります:

  1. loading.jsファイルを使用する
  2. Reactの<Suspense>コンポーネントを使用する

loading.jsを使用する場合

データがフェッチされている間にページ全体をストリーミングするには、ページと同じフォルダにloading.jsファイルを作成できます。例えば、app/blog/page.jsをストリーミングするには、app/blogフォルダ内にファイルを追加します。

loading.jsファイルを含むブログフォルダ構造
app/blog/loading.tsx
TypeScript
export default function Loading() {
  // ここでローディングUIを定義します
  return <div>Loading...</div>
}

ナビゲーション時、ユーザーはページがレンダリングされている間、レイアウトとローディング状態をすぐに見ることができます。レンダリングが完了すると、新しいコンテンツが自動的に置き換えられます。

ローディングUI

裏側では、loading.jslayout.jsの中にネストされ、page.jsファイルとその下の子要素を自動的に<Suspense>バウンダリでラップします。

loading.jsの概要

このアプローチはルートセグメント(レイアウトとページ)に適していますが、より細かいストリーミングには<Suspense>を使用できます。

<Suspense>を使用する場合

<Suspense>を使用すると、ページのどの部分をストリーミングするかをより細かく制御できます。例えば、<Suspense>バウンダリの外側にあるページコンテンツをすぐに表示し、バウンダリ内のブログ投稿リストをストリーミングできます。

app/blog/page.tsx
TypeScript
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を使用してコンポーネントのローディング状態をプレビューおよび検査できます。

API リファレンス

このページで紹介した機能について詳しく知るには、API リファレンスをご覧ください。