Create Branch

Next.jsにおけるデータフェッチの配置についての考え方

Published on 2024年11月10日

メモ

背景

Next.jsアプリケーションを開発する際、データフェッチをどこに配置するかは重要な設計上であると考えています。
一般的には「Colocation」の原則に従って末端コンポーネントでデータをフェッチすることが推奨されていました。

しかしながら、QiiitaやZenで投稿されている記事を読んでみても、記事によってフェッチの実行位置が異なり、
データフェッチにフォーカスしたものを見つけることができなかったため、実装方針を考えてみようとおもいました。 

Colocationとは?

Colocationとは、関連するコードやデータを物理的に近い場所に配置するという設計原則です。
本記事については、Next.jsのデータを使用するコンポーネントでそのデータをフェッチのみについて考えております。

実装アプローチの比較

0. 前提

実装の観点上、各実装における責務を分けて依存関係をできる限り持たせない状態を作ることを意識しており、下記のディレクトリ構成となっていおります。

 
├── 📂 apps
│   ├── 📂 api
│   ├── 📄 page.tsx
│   └── 📂 blog
│       └── 📂 [id]
│           └── 📄 page.tsx
│
├── 📂 hooks     // featuresで定義されているAPIを参照してhooksで実行できるようように定義
│   └── 📂 blog
│       └── 📄 index.ts
│
├── 📂 features  // 外部連携しているAPIを定義
│   └── 📂 blog
│       └── 📄 index.ts
│
└── 📂 utils    // microCMSのClientを定義
    └── 📂 microcms
        └── 📄 client.ts

1. 末端コンポーネントでのデータフェッチ

このアプローチでは、データを実際に使用するコンポーネントで直接フェッチを行います。

// component/atom/cardList/index.ts
import { Card } from "@/components/molecules/card";
import { Blog } from "@/types/blog";
import { useBlogs } from "@/hooks/blog";

const styles = {
    cardList: 'grid grid-cols-1 md:grid-cols-2 gap-6 w-full list-none',
}

export const CardList = () => {
    const { blogArticles, loading } = useBlogs();

----------------------一部省略--------------------------
    return (
        <ol className={ styles.cardList }>
            {blogArticles.map((blog: Blog) => (
                <Card key={blog.id} blog={blog} />
            ))}
        </ol>
    );
}

メリット

  • データの使用場所とフェッチが近いため、データフローが追跡しやすい
  • コンポーネントの独立性が高まる
  • 必要なデータのみをフェッチできる

デメリット

  • コンポーネントの責務が増える
  • データフェッチのロジックがコンポーネント間で重複する可能性がある
  • コンポーネントの再利用性が低下する可能性がある

2. ページレベルでのデータフェッチ

このアプローチでは、ページコンポーネント(page.tsx)でデータをフェッチし、必要なコンポーネントにプロップスとして渡す。

// component/atom/cardList/index.ts
import { Card } from "@/components/molecules/card";
import { Blog } from "@/types/blog";

type BlogProps = {
    blogs: Blog[];
}

const styles = {
    cardList: 'grid grid-cols-1 md:grid-cols-2 gap-6 w-full list-none',
}

export const CardList = ({ blogs }: BlogProps) => {
    return (
        <ol className={ styles.cardList }>
            {blogs.length === 0 ? (
            <p className="text-gray-600">投稿がありません</p>
            ) : (
            blogs.map((blog: Blog) => (
                <Card key={blog.id} blog={blog} />
            ))
            )}
        </ol>
    );
}

メリット

  • データフェッチの責務が明確
  • ページ全体のデータ依存関係が可視化される
  • コンポーネントが純粋な表示の責務のみを持つ

デメリット

  • プロップスドリリングが発生する可能性がある
  • ページコンポーネントが肥大化する可能性がある

組織的な考慮事項

実際のプロジェクトでは、以下のような要因を考慮してアプローチを選択する必要があると考えています。

チーム構成による影響

  • エンジニアの経験値
  • フロントエンド知見の深さ
  • チームの規模と構成

プロダクトの方針

  • 開発速度とPDCAサイクルの重要性
  • ドメイン駆動設計の採用
  • 保守性と変更容易性の優先度

推奨アプローチ

組織の状況によって、以下のような選択基準を設けることができる。

ページレベルでのフェッチが適している場合

  • チームメンバーの経験が比較的浅い
  • コードの可読性を重視している
  • 変更容易性を優先している
  • 責務の明確な分離が必要

末端コンポーネントでのフェッチが適している場合

  • チームメンバーが experienced
  • パフォーマンスの最適化を重視している
  • コンポーネントの独立性を重視している
  • マイクロフロントエンドなどの分散アーキテクチャを採用している

まとめ

データフェッチの配置は、技術的な観点だけでなく、組織的な文脈も考慮して決定する必要があるという考えに至りました。
Next.jsの推奨パターンは重要な参考になりますが、プロジェクトやチームの状況に応じて、適切なアプローチを選択することが重要です。

また、アプローチの方法候補が複数考えている場合、アプローチ方法ごとに、メリット・デメリットのトレードオフが発生します。
トーレドオフを考えた上で現状のプロジェクトに適したものを適用する必要があります。

リファレンス

hirotobeat