コンテンツにスキップ
App Routerはじめにデータ取得

データの取得

このページでは、Server Component と Client Component でどのようにデータを取得できるか、またデータに依存するコンポーネントをどのようにストリーミングできるかについて説明します。

データの取得

サーバーコンポーネント

Server Component では、以下を使用してデータを取得できます。

  1. fetch API
  2. ORM またはデータベース

fetch API を使用する

fetch API でデータを取得するには、コンポーネントを非同期関数に変換し、fetch 呼び出しを await します。たとえば、

app/blog/page.tsx
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>
  )
}

知っておくと良いこと

  • fetch 応答はデフォルトではキャッシュされません。ただし、Next.js はルートをプリレンダリングし、パフォーマンス向上のために出力をキャッシュします。 動的レンダリングを採用したい場合は、{ cache: 'no-store' } オプションを使用してください。 fetch API リファレンスを参照してください。
  • 開発中は、fetch 呼び出しをログに記録することで、可視性とデバッグを向上させることができます。 logging API リファレンスを参照してください。

ORM またはデータベースを使用する

Server Component はサーバーでレンダリングされるため、ORM またはデータベースクライアントを使用して安全にデータベースクエリを実行できます。コンポーネントを非同期関数に変換し、呼び出しを await します。

app/blog/page.tsx
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 Component でデータを取得するには、次の 2 つの方法があります。

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

use フックによるストリーミングデータ

React の use フック を使用して、サーバーからクライアントへデータをストリーミングできます。まず Server Component でデータを取得し、その Promise を Client Component に props として渡します。

app/blog/page.tsx
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
 
export default function Page() {
  // Don't await the data fetching function
  const posts = getPosts()
 
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Posts posts={posts} />
    </Suspense>
  )
}

次に、Client Component で use フックを使用して Promise を読み取ります。

app/ui/posts.tsx
'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> 境界 でラップされています。これは、Promise が解決される間、フォールバックが表示されることを意味します。 ストリーミングの詳細についてはこちらをご覧ください。

コミュニティライブラリ

Client Component でデータを取得するには、SWRReact Query などのコミュニティライブラリを使用できます。これらのライブラリには、キャッシング、ストリーミングなどの機能に関する独自のセマンティクスがあります。たとえば、SWR の場合

app/blog/page.tsx
'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>
  )
}

リクエストの重複排除とデータキャッシュ

fetch リクエストの重複を排除する 1 つの方法は、リクエストのメモ化を使用することです。このメカニズムにより、単一のレンダリングパスで同じ URL とオプションを使用する GET または HEADfetch 呼び出しは、1 つのリクエストに結合されます。これは自動的に行われ、fetch に AbortSignal を渡すことでオプトアウトできます。

リクエストのメモ化は、リクエストの有効期間にスコープされます。

Next.js の データキャッシュを使用して fetch リクエストの重複を排除することもできます。たとえば、fetch オプションで cache: 'force-cache' を設定することで実現できます。

データキャッシュにより、現在のレンダリングパスと着信リクエスト間でデータを共有できます。

fetch を使用しておらず、代わりに ORM またはデータベースを直接使用している場合は、データアクセスを React の cache 関数でラップできます。

app/lib/data.ts
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
 
export const getPost = cache(async (id: string) => {
  const post = await db.query.posts.findFirst({
    where: eq(posts.id, parseInt(id)),
  })
})

ストリーミング

注意:以下のコンテンツは、アプリケーションで cacheComponents 設定オプションが有効になっていることを前提としています。このフラグは Next.js 15 canary で導入されました。

Server Component でデータを取得すると、データはリクエストごとにサーバーで取得およびレンダリングされます。データリクエストが遅い場合、すべてのデータが取得されるまでルート全体のレンダリングがブロックされます。

初期ロード時間とユーザーエクスペリエンスを向上させるために、ストリーミングを使用してページの HTML を小さなチャンクに分割し、それらのチャンクをサーバーからクライアントに段階的に送信できます。

How Server Rendering with Streaming Works

アプリケーションでストリーミングを実装するには、次の 2 つの方法があります。

  1. loading.js ファイルでページをラップする
  2. <Suspense> でコンポーネントをラップする

loading.js を使用する

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

Blog folder structure with loading.js file
app/blog/loading.tsx
export default function Loading() {
  // Define the Loading UI here
  return <div>Loading...</div>
}

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

Loading UI

内部的には、loading.jslayout.js の中にネストされ、page.js ファイルとそれ以下のすべての子を <Suspense> 境界で自動的にラップします。

loading.js overview

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

<Suspense> を使用する

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

app/blog/page.tsx
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
 
export default function BlogPage() {
  return (
    <div>
      {/* This content will be sent to the client immediately */}
      <header>
        <h1>Welcome to the Blog</h1>
        <p>Read the latest posts below.</p>
      </header>
      <main>
        {/* Any content wrapped in a <Suspense> boundary will be streamed */}
        <Suspense fallback={<BlogListSkeleton />}>
          <BlogList />
        </Suspense>
      </main>
    </div>
  )
}

意味のあるローディング状態の作成

インスタントローディング状態は、ナビゲーション後にすぐにユーザーに表示されるフォールバック UI です。最適なユーザーエクスペリエンスのために、アプリが応答していることをユーザーに理解させるのに役立つ、意味のあるローディング状態を設計することをお勧めします。たとえば、スケルトンやスピナー、またはカバー写真やタイトルなど、将来の画面の小さくも意味のある部分を使用できます。

開発中は、React Devtools を使用して、コンポーネントのローディング状態をプレビューおよび検査できます。

シーケンシャルなデータ取得

シーケンシャルなデータ取得は、ツリー内のネストされたコンポーネントがそれぞれ独自のデータを取得し、リクエストが重複排除されない場合に発生し、応答時間の遅延につながります。

Sequential and Parallel Data Fetching

このパターンが必要な場合があります。それは、あるフェッチが別のフェッチの結果に依存する場合です。

たとえば、<Playlists> コンポーネントは、<Playlists>artistID prop に依存しているため、<Artist> コンポーネントがデータの取得を完了するまで、データの取得を開始しません。

app/artist/[username]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ username: string }>
}) {
  const { username } = await params
  // Get artist information
  const artist = await getArtist(username)
 
  return (
    <>
      <h1>{artist.name}</h1>
      {/* Show fallback UI while the Playlists component is loading */}
      <Suspense fallback={<div>Loading...</div>}>
        {/* Pass the artist ID to the Playlists component */}
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}
 
async function Playlists({ artistID }: { artistID: string }) {
  // Use the artist ID to fetch playlists
  const playlists = await getArtistPlaylists(artistID)
 
  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  )
}

ユーザーエクスペリエンスを向上させるには、React の <Suspense> を使用して、データ取得中に fallback を表示する必要があります。これにより、ストリーミングが有効になり、シーケンシャルなデータリクエストによってルート全体がブロックされるのを防ぎます。

パラレルなデータ取得

パラレルなデータ取得は、ルート内のデータリクエストが早期に開始され、同時に開始される場合に発生します。

デフォルトでは、レイアウトとページはパラレルにレンダリングされます。そのため、各セグメントは可能な限り早くデータの取得を開始します。

ただし、任意のコンポーネント内でも、複数の async/await リクエストは、互いの後に配置されている場合、シーケンシャルになる可能性があります。たとえば、getArtist が解決されるまで getAlbums はブロックされます。

app/artist/[username]/page.tsx
import { getArtist, getAlbums } from '@/app/lib/data'
 
export default async function Page({ params }) {
  // These requests will be sequential
  const { username } = await params
  const artist = await getArtist(username)
  const albums = await getAlbums(username)
  return <div>{artist.name}</div>
}

fetch を呼び出して複数のリクエストを開始し、その後 Promise.all でそれらを await します。リクエストは fetch が呼び出されるとすぐに開始されます。

app/artist/[username]/page.tsx
import Albums from './albums'
 
async function getArtist(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}`)
  return res.json()
}
 
async function getAlbums(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`)
  return res.json()
}
 
export default async function Page({
  params,
}: {
  params: Promise<{ username: string }>
}) {
  const { username } = await params
 
  // Initiate requests
  const artistData = getArtist(username)
  const albumsData = getAlbums(username)
 
  const [artist, albums] = await Promise.all([artistData, albumsData])
 
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums} />
    </>
  )
}

知っておくと便利: Promise.all を使用しているときに 1 つのリクエストが失敗すると、操作全体が失敗します。これを処理するには、代わりに Promise.allSettled メソッドを使用できます。

データのプリロード

ユーティリティ関数を作成してデータをプリロードできます。この関数は、ブロッキングリクエストの上に早期に呼び出されます。<Item>checkIsAvailable() 関数に基づいて条件付きでレンダリングされます。

<Item/> がレンダリングされるまでにデータがすでにフェッチされているように、checkIsAvailable() の前に preload() を呼び出すことができます。

app/item/[id]/page.tsx
import { getItem, checkIsAvailable } from '@/lib/data'
 
export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  // starting loading item data
  preload(id)
  // perform another asynchronous task
  const isAvailable = await checkIsAvailable()
 
  return isAvailable ? <Item id={id} /> : null
}
 
export const preload = (id: string) => {
  // void evaluates the given expression and returns undefined
  // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
  void getItem(id)
}
export async function Item({ id }: { id: string }) {
  const result = await getItem(id)
  // ...
}

さらに、React の cache 関数server-only パッケージ を使用して、再利用可能なユーティリティ関数を作成できます。このアプローチにより、データ取得関数をキャッシュし、サーバーでのみ実行されるようにすることができます。

utils/get-item.ts
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
 
export const preload = (id: string) => {
  void getItem(id)
}
 
export const getItem = cache(async (id: string) => {
  // ...
})