ReDevLab
Web開発 19分で読める

Next.js Server Componentsデータ取得ベストプラクティス完全ガイド

Next.js 15対応。Server Componentsでのデータ取得パターン、キャッシュ戦略、並列フェッチ、エラーハンドリングを実践コード付きで解説します。

編集部
Next.js Server Componentsデータ取得ベストプラクティス完全ガイド

Next.js Server Componentsのデータ取得ベストプラクティスを押さえれば、パフォーマンスとセキュリティを両立したアプリケーションを構築できます。Server Componentsはサーバー側で直接データベースやAPIにアクセスできるため、APIキーの漏洩リスクを排除しつつ、クライアントへのJavaScript転送量も削減できます。たとえば、Promise.allによる並列フェッチでウォーターフォールを解消し、Suspenseによるストリーミングでユーザー体感速度を改善する——こうしたパターンを組み合わせることで、TTFB(Time To First Byte)やFCP(First Contentful Paint)の大幅な短縮が可能です。この記事では、Next.js 15(2024年10月リリース)を基準に、データ取得・キャッシュ戦略・エラーハンドリング・セキュリティ対策まで、本番環境で使える実装パターンをコード付きで解説します。

前提条件と環境構築

この記事のコード例はすべてNext.js 15を基準にしています。Next.js 14以前ではfetchのデフォルトキャッシュ動作が異なるため、バージョンの確認を最初に行ってください。

必要なツールとバージョン

動作確認済みの環境は以下のとおりです。

ツールバージョン備考
Node.js20.x 以上(2026年1月13日パッチ適用済みAsyncLocalStorage脆弱性修正を含むビルド
Next.js15.x2024年10月21日リリース。React 19・Turbopack Dev安定版を同梱
React19.xNext.js 15が依存
npm / pnpmnpm 10.x または pnpm 9.xパッケージマネージャーはどちらでも可
TypeScript5.5 以上推奨。型安全なデータ取得に必須

Node.jsのバージョンには特に注意してください。 2026年1月に公表されたAsyncLocalStorageのスタックオーバーフロー脆弱性により、パッチ未適用のNode.jsでは本番環境でプロセスがコード7で異常終了する恐れがあります。node -v で必ず確認しましょう。

プロジェクトセットアップ手順

  1. Next.js 15プロジェクトを作成する

create-next-app でApp Router構成のプロジェクトを生成します。--app フラグによりServer Componentsが有効な構成が作られます。

# macOS / Linux
npx create-next-app@latest my-app --app --typescript --tailwind --eslint
cd my-app
# Windows PowerShell
npx create-next-app@latest my-app --app --typescript --tailwind --eslint
Set-Location my-app
  1. Node.jsバージョンを固定する

チームメンバー間でのバージョン差異を防ぐため、.node-version ファイルを作成します。

# macOS / Linux
echo "20.18.2" > .node-version
# Windows PowerShell
"20.18.2" | Out-File -Encoding utf8 .node-version
  1. 開発サーバーを起動して動作を確認する

起動後、http://localhost:3000 にアクセスできれば準備完了です。

# macOS / Linux
npm run dev
# Windows PowerShell
npm run dev

ターミナルに以下のような出力が表示されます。

▲ Next.js 15.x.x
- Local: http://localhost:3000
✓ Ready in 2.1s

Next.js 15ではfetchのデフォルトキャッシュがno-storeに変更されました。Next.js 14からの移行時は、既存のfetch呼び出しに cache: 'force-cache' を明示的に追加する必要があります。この変更の具体的な影響と対処法は、後述の「キャッシュ戦略とrevalidate」セクションで詳しく解説します。

Server Componentsでのデータ取得パターンと並列フェッチ

Server Componentsを使うと、コンポーネント内で直接async/awaitが書けます。APIキーやデータベース接続情報をクライアントに露出させずにデータを取得できる点が最大のメリットです。ここでは、Next.js 15(2024年10月リリース)を基準に、実践的な3つのパターンを紹介します。

async/awaitによる直接フェッチとRequest Memoization

Server Componentsでは、コンポーネント関数にasyncをつけるだけでデータ取得が完結します。

// app/posts/page.tsx(Next.js 15)
export default async function PostsPage() {
  const res = await fetch("https://api.example.com/posts", {
    cache: "force-cache", // Next.js 15ではデフォルトがno-store
  });
  const posts = await res.json();

  return (
    <ul>
      {posts.map((post: { id: number; title: string }) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

ポイントはcacheオプションの明示指定です。Next.js 15ではfetchのデフォルトがno-storeに変更されました。キャッシュを効かせたい場合はforce-cacheを必ず指定してください。

もう一つ知っておきたいのが、Request Memoization(リクエストメモ化)の仕組みです。同一レンダリングツリー内で同じURLへのfetchが複数回呼ばれても、自動的に重複排除されます。親子コンポーネントで同じAPIを呼んでも、実際のリクエストは1回だけです。

Promise.allによる並列データ取得でウォーターフォールを回避する

複数のデータソースからfetchする際、awaitを直列に並べると「ウォーターフォール」が発生します。リクエストAの完了を待ってからリクエストBが始まるため、合計待ち時間が長くなります。

Promise.allで並列化しましょう。

// app/dashboard/page.tsx
async function getUser() {
  const res = await fetch("https://api.example.com/user");
  return res.json();
}

async function getOrders() {
  const res = await fetch("https://api.example.com/orders");
  return res.json();
}

export default async function DashboardPage() {
  // 並列実行:両方のリクエストを同時に開始
  const [user, orders] = await Promise.all([getUser(), getOrders()]);

  return (
    <div>
      <h1>{user.name}さんのダッシュボード</h1>
      <p>注文件数: {orders.length}</p>
    </div>
  );
}

直列の場合、各APIが200msかかると合計400ms待ちます。Promise.allなら約200msで済みます。一方のAPIが失敗するとすべて失敗する点には注意してください。部分的な成功を許容したい場合はPromise.allSettledを使います。

Prisma接続とシングルトンパターンの実装

Server ComponentsからPrismaなどのORMで直接DBへアクセスする場合、開発環境のホットリロードでクライアントインスタンスが大量生成される問題があります。Prisma公式が推奨するシングルトンパターンで防ぎましょう。

// lib/prisma.ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}

globalThisにインスタンスを保持することで、ホットリロード時も同じクライアントを再利用します。本番環境ではモジュールキャッシュが効くため、globalThisへの代入はスキップしています。

Server Componentからの呼び出しはシンプルです。

// app/users/page.tsx
import { prisma } from "@/lib/prisma";

export default async function UsersPage() {
  const users = await prisma.user.findMany({
    take: 20,
    orderBy: { createdAt: "desc" },
  });

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

サーバーレス環境(Vercelなど)ではコネクション枯渇にも注意が必要です。Prismaのconnection_limitパラメータをDATABASE_URLに追加して、プール数を制限してください。

キャッシュ戦略・Suspense・エラーハンドリングの実装

Next.js 15ではキャッシュの挙動が大きく変わりました。ストリーミングやエラー制御と組み合わせることで、高速かつ堅牢なデータ取得を実現できます。

Next.js 15のキャッシュ破壊的変更とrevalidate設定

2024年10月リリースのNext.js 15では、fetchのデフォルトキャッシュがforce-cacheからno-storeに変更されました。GET Route Handlersも同様にキャッシュされなくなっています。

つまり、明示的に指定しない限り毎回リクエストが発生します。以下のようにrevalidateを設定してください。

// 時間ベースの再検証:60秒ごとにキャッシュを更新
const data = await fetch("https://api.example.com/posts", {
  next: { revalidate: 60 },
});

// オンデマンド再検証:タグ指定で任意のタイミングに無効化
const data = await fetch("https://api.example.com/posts", {
  next: { tags: ["posts"] },
});

タグ付けしたキャッシュは、Server ActionからrevalidateTag("posts")を呼び出すことで即座に無効化できます。

unstable_cache・React.cache・use cacheの使い分け

Prismaなどfetch APIを使わないデータ取得では、3つのキャッシュ機構を目的別に選びます。

機構スコープ用途
React.cache単一レンダリングリクエスト内のメモ化(同じ関数呼び出しの重複排除)
unstable_cache複数リクエストレンダリングをまたぐデータキャッシュ(実験的API)
"use cache"複数リクエストunstable_cacheの後継。コンポーネントやルートもキャッシュ可能

公式ドキュメントではunstable_cacheから"use cache"ディレクティブへの移行を推奨しています。新規プロジェクトでは"use cache"を選んでください。

// use cacheの例:関数単位でキャッシュ
async function getUser(id: string) {
  "use cache";
  return await prisma.user.findUnique({ where: { id } });
}

loading.tsx・error.tsx・Suspenseによるストリーミングとエラー制御

loading.tsxを配置すると、ページが自動的に<Suspense>境界でラップされます。これによりTTFB(Time To First Byte)とFCP(First Contentful Paint)が短縮され、特に低速デバイスでのTTI改善が見込めます。

コンポーネント単位で制御したい場合は、<Suspense>を直接使います。

// ページ全体ではなく、遅いデータだけにSuspenseを適用
export default function Dashboard() {
  return (
    <div>
      <h1>ダッシュボード</h1>
      <Suspense fallback={<p>売上データを読み込み中...</p>}>
        <SalesChart />
      </Suspense>
      <Suspense fallback={<p>通知を読み込み中...</p>}>
        <Notifications />
      </Suspense>
    </div>
  );
}

error.tsxはReact Error Boundaryとして機能し、予期しない例外をキャッチしてフォールバックUIを表示します。ここで注意すべきは、redirectの扱いです。redirectは内部でエラーをthrowするため、try-catchの外で呼び出す必要があります。

// NG: redirectがcatchに捕捉される
try {
  const data = await fetchData();
  if (!data) redirect("/not-found");
} catch (e) {
  // redirectのthrowもここに来てしまう
}

// OK: try-catchの外でredirectを呼ぶ
const data = await fetchData().catch(() => null);
if (!data) redirect("/not-found");

本番環境のセキュリティとServer/Client境界設計

Server Componentsを本番環境で運用するには、セキュリティパッチの適用、コンポーネント境界の適切な設計、パフォーマンス指標の監視が欠かせません。ここでは2026年に発覚した脆弱性への対策から、実践的な設計パターンまでを解説します。

2026年1月のRSC脆弱性(CVE-2025-66478)とAsyncLocalStorage対策

2026年1月、RSC(React Server Components)プロトコルにCVSS 10.0の脆弱性CVE-2025-66478が発見されました。RCE(Remote Code Execution、リモートコード実行)が可能な深刻度の高い問題です。

パッチ適用済みのNext.jsへ即座にアップデートしてください。

# macOS / Linux
npm install next@latest react@latest react-dom@latest
# Windows PowerShell
npm install next@latest react@latest react-dom@latest

同月にはNode.jsのAsyncLocalStorage(Next.jsが内部で使用する非同期コンテキスト管理機構)にも脆弱性が見つかっています。最大再帰深度に到達するとNode.jsがコード7で異常終了するため、2026年1月13日リリースのパッチを必ず適用してください。

現在のNode.jsバージョンは以下で確認できます。

# macOS / Linux
node -v
# Windows PowerShell
node -v

childrenプロップパターンとuse client配置戦略

Server ComponentとClient Componentの境界設計で最も有効なのが、childrenプロップパターンです。Client ComponentのchildrenにServer Componentを渡すことで、子コンポーネントをサーバー側で実行したまま保てます。

// app/components/InteractiveWrapper.tsx
"use client";

export function InteractiveWrapper({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && children}
    </div>
  );
}
// app/page.tsx(Server Component)
import { InteractiveWrapper } from "./components/InteractiveWrapper";
import { ServerDataView } from "./components/ServerDataView";

export default function Page() {
  return (
    <InteractiveWrapper>
      {/* ServerDataViewはサーバー側で実行される */}
      <ServerDataView />
    </InteractiveWrapper>
  );
}

"use client"ディレクティブはできるだけ末端のコンポーネントに配置するのがポイントです。ページ全体に付与すると、データ取得がすべてクライアント側に移動し、APIキーの露出リスクやバンドルサイズ増大を招きます。

サーバーレス環境でのパフォーマンス最適化指標

Server Componentsのストリーミングを活用すると、3つの指標を改善できます。

指標意味ストリーミングの効果
TTFB最初の1バイトが届くまでの時間シェルHTMLを先行送信し短縮
FCP最初のコンテンツが描画されるまでの時間loading.tsxで即座にフォールバック表示
TTIユーザーが操作可能になるまでの時間JSバンドル削減により特に低速デバイスで改善

loading.tsxをルートセグメントに配置するだけで、<Suspense>境界が自動生成されます。ページ単位のストリーミングが有効になり、データ取得完了を待たずにUIの一部を先行表示できます。

サーバーレス環境ではPrismaのコネクション枯渇にも注意が必要です。Prisma公式ドキュメントが推奨するシングルトンパターンで、インスタンスの重複生成を防いでください。

まとめ:本番デプロイ前に確認したい3つのアクション

ここまで解説したパターンを本番環境に適用する際、最初に取り組むべきアクションを3つ挙げます。

1. セキュリティパッチの適用状況を確認する

2026年1月に公表されたRSCプロトコルの脆弱性(CVE-2025-66478)はCVSS 10.0のRCE(Remote Code Execution、リモートコード実行)です。また、AsyncLocalStorageのスタックオーバーフロー脆弱性も同月にパッチがリリースされています。next -vnode -v を実行し、修正済みバージョンで動作しているか今すぐ確認してください。

2. fetchのキャッシュ設定を棚卸しする

Next.js 14以前から移行したプロジェクトでは、fetchのデフォルトがforce-cacheからno-storeに変わった影響で、意図せずキャッシュが無効になっているケースがあります。grep -r "fetch(" app/ でプロジェクト内のfetch呼び出しを一覧化し、各リクエストに適切なcacheオプションとnext.revalidateが設定されているか点検しましょう。

3. 段階的にuse cacheディレクティブへ移行する

unstable_cacheは実験的APIのままです。公式ドキュメントではuse cacheディレクティブへの移行が推奨されています。新規実装ではuse cacheを採用し、既存のunstable_cacheは次のスプリントで計画的に置き換えるのが現実的な進め方です。

この記事で扱ったパターンは、あくまでNext.js 15時点のベストプラクティスです。App Routerのキャッシュ機構は活発に進化しており、dynamicIOフラグや部分プリレンダリング(PPR)など、次のバージョンで安定化が見込まれる機能も控えています。Next.js公式ブログRFCリポジトリをウォッチしておくと、キャッシュ戦略の変更をいち早くキャッチできます。

参考文献

s

この記事を書いた人

数学科出身のWebエンジニア

共有: