Dev Study
← 解説「データ取得 — サーバーでの fetch」に戻る

サンプルコードで身につける: データ取得 — サーバーでの fetch

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小のサーバーfetch

Server Component を async にして await fetch するだけ、というデータ取得の最小形です。useEffect もローディング状態の変数も登場せず、「取得して、変数に入れて、JSX に渡す」が一直線に書けます。

// app/todos/page.tsx — async を付ければ await が使える
type Todo = { id: number; title: string };

export default async function TodosPage() {
  const res = await fetch("https://api.example.com/todos");
  const todos: Todo[] = await res.json();

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}
// useEffect も loading 状態の管理も不要。

2動的ルートとfetchの組み合わせ

URL の [id] でどのデータを取るかを決め、API から1件取得して表示する例です。動的ルート + fetch + notFound の3点セットは、記事詳細や商品詳細などあらゆる「詳細ページ」の基本形になります。

// app/posts/[id]/page.tsx
import { notFound } from "next/navigation";

type Post = { id: number; title: string; body: string };

export default async function PostPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const res = await fetch("https://api.example.com/posts/" + id);
  if (!res.ok) notFound(); // API が 404 ならこのページも 404
  const post: Post = await res.json();
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

3Promise.all で並行取得

互いに関係のない複数のデータは、Promise.all で同時に取りに行くと待ち時間が最長の1本分で済みます。プロフィールと投稿一覧のように複数 API を呼ぶダッシュボード系のページで、表示速度に直結する定番テクニックです。

// app/dashboard/page.tsx — 2つの API を同時に呼ぶ
type User = { id: number; displayName: string };
type Post = { id: number; title: string };

export default async function DashboardPage() {
  // 直列に await すると待ち時間が足し算になる。並行なら最長1本分
  const [userRes, postsRes] = await Promise.all([
    fetch("https://api.example.com/me"),
    fetch("https://api.example.com/me/posts"),
  ]);
  const user: User = await userRes.json();
  const posts: Post[] = await postsRes.json();

  return (
    <main>
      <h1>{user.displayName} さんのダッシュボード</h1>
      <p>投稿数: {posts.length}</p>
    </main>
  );
}

4直列と並行の待ち時間を体感する

fetch を疑似的な遅延付き関数に置き換え、並行取得で所要時間が短くなることを実測する例です。await を縦に並べるか Promise.all でまとめるかという書き方の違いが、そのままページの表示速度の違いになることを確かめられます。

TypeScript

5検索結果ページ

URL の ?q=キーワード を searchParams で受け取り、その値で API を呼んで結果を表示する実務的な例です。検索結果が URL に残るのでブックマークや共有ができる、サーバー側検索ページの基本形です。

// app/search/page.tsx → /search?q=nextjs で検索結果を表示
type Item = { id: number; title: string };

export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ q?: string }>;
}) {
  const { q } = await searchParams;
  if (!q) return <p>キーワードを入力してください</p>;

  const res = await fetch(
    "https://api.example.com/search?q=" + encodeURIComponent(q)
  );
  const items: Item[] = await res.json();

  return (
    <main>
      <h1>{q}」の検索結果 {items.length}</h1>
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </main>
  );
}