フォルダ構成
📂 Next.jsのフォルダ構成:状態管理の統合
Section titled “📂 Next.jsのフォルダ構成:状態管理の統合”Next.jsプロジェクトのフォルダ構成は、状態管理ライブラリを導入することでさらに複雑になります。クリーンアーキテクチャの原則と、機能ベースのBulletproof Reactの思想を組み合わせることで、状態管理コードを整理し、保守性とスケーラビリティを維持できます。
なぜフォルダ構成が重要なのか
Section titled “なぜフォルダ構成が重要なのか”問題のあるフォルダ構成の影響
Section titled “問題のあるフォルダ構成の影響”問題1: 機能が散在する構成
// 問題のある構成: ファイルタイプで整理/components /Button.tsx /UserCard.tsx /OrderList.tsx/hooks /useUser.ts /useOrder.ts/utils /userUtils.ts /orderUtils.ts
// 問題点:// - 関連するコードが散在している// - 機能の全体像が把握しにくい// - リファクタリングが困難// - チーム開発でコンフリクトが起きやすい問題2: 責務の混在
📚 Reactの基礎について: useState、useEffect、コンポーネントの詳細については、Reactガイドを参照してください。
// 問題のあるコード: コンポーネントにビジネスロジックが混在// 注意: useStateとuseEffectの詳細については、Reactガイドを参照してくださいexport default function UserCard({ userId }: { userId: string }) { const [user, setUser] = useState(null);
useEffect(() => { // 問題: API呼び出しがコンポーネント内にある fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => setUser(data)); }, [userId]);
// 問題: ビジネスロジックがコンポーネント内にある const calculateAge = (birthDate: string) => { const today = new Date(); const birth = new Date(birthDate); return today.getFullYear() - birth.getFullYear(); };
return <div>{user?.name} ({calculateAge(user?.birthDate)})</div>;}適切なフォルダ構成の価値
Section titled “適切なフォルダ構成の価値”解決: 機能ベースの構成
// 解決: 機能ごとに整理/features /users /components /UserCard.tsx /hooks /useUser.ts /api /userApi.ts /utils /userUtils.ts /types /user.ts
// 利点:// - 関連するコードが一箇所に集約// - 機能の全体像が把握しやすい// - リファクタリングが容易// - チーム開発でコンフリクトが起きにくい解決: 責務の分離
📚 カスタムフックの詳細: カスタムフックの作成方法については、ReactガイドのuseStateとuseEffectの「カスタムフックへの分離」セクションを参照してください。
// 解決: ビジネスロジックを分離// 注意: useState、useEffect、カスタムフックの詳細については、Reactガイドを参照してくださいexport function useUser(userId: string) { const [user, setUser] = useState<User | null>(null);
useEffect(() => { userApi.getUser(userId).then(setUser); }, [userId]);
return user;}
// features/users/utils/userUtils.tsexport function calculateAge(birthDate: string): number { const today = new Date(); const birth = new Date(birthDate); return today.getFullYear() - birth.getFullYear();}
// features/users/components/UserCard.tsxexport default function UserCard({ userId }: { userId: string }) { const user = useUser(userId); const age = user ? calculateAge(user.birthDate) : null;
return <div>{user?.name} ({age})</div>;}1. 概念的なレイヤーの分離 🏛️
Section titled “1. 概念的なレイヤーの分離 🏛️”クリーンアーキテクチャでは、コードを以下の3つのレイヤーに分離します。この階層構造により、外側のレイヤー(UI)は内側のレイヤー(データ)に依存しますが、その逆はありません。
-
UI層(プレゼンテーション) 🖥️
- ユーザーインターフェース(UI)の表示と入力処理を担当します。Next.jsでは、
app/ディレクトリ内のコンポーネントやページがこれに該当します。
- ユーザーインターフェース(UI)の表示と入力処理を担当します。Next.jsでは、
-
アプリケーション層(ビジネスロジック) 🛠️
- アプリケーション固有の機能やルールを定義します。ユーザー作成や注文処理といった、具体的なビジネスロジックがここに集約されます。
-
データアクセス層(インフラストラクチャ) 💾
- データベース、外部API、ファイルシステムなどのデータソースとのやり取りを扱います。この層は、データ永続化の詳細を抽象化します。
2. フォルダ構成への適用 📁
Section titled “2. フォルダ構成への適用 📁”これらのレイヤーを具体的なNext.jsのフォルダ構成に落とし込むには、機能ベースのBulletproof Reactの思想が役立ちます。
コアディレクトリ
Section titled “コアディレクトリ”-
app/- Next.jsのルーティングとUI層を担います。ここには、ページ (
page.tsx) や共有レイアウト (layout.tsx) など、ユーザーに直接表示されるコードを配置します。ビジネスロジックはここには含めず、シンプルに保ちます。
- Next.jsのルーティングとUI層を担います。ここには、ページ (
-
src/- プロジェクトの主要なコードを格納する推奨ディレクトリです。この中に、機能や共通コードを分離します。
分離されたレイヤー
Section titled “分離されたレイヤー”src/features/(アプリケーション層)- アプリケーションの主要な機能(例:auth、user、blog)をフォルダごとに整理します。各機能フォルダ内には、その機能に特化したコンポーネント、フック、API呼び出しなどが含まれます。
/src/features/auth/├── components/├── hooks/└── api/src/lib/(データアクセス層)- データベース接続、外部APIクライアント、認証ライブラリなど、アプリケーション全体で再利用されるインフラストラクチャ関連のコードを配置します。
/src/lib/├── db.ts // データベース接続├── auth.ts // 認証ヘルパー└── utils.ts // 汎用ユーティリティ-
src/components/(UI層の共有コンポーネント)- アプリケーションの複数の場所で再利用される、汎用的なUIコンポーネント(例:
Button.tsx、Modal.tsx)を配置します。
- アプリケーションの複数の場所で再利用される、汎用的なUIコンポーネント(例:
-
src/styles/- グローバルなスタイルやテーマを定義します。
3. 状態管理の統合 💾
Section titled “3. 状態管理の統合 💾”状態管理コードを導入する際は、そのコードをUIから分離し、アプリケーション層に配置するのがベストプラクティスです。
機能ごとの状態管理
Section titled “機能ごとの状態管理”特定の機能に関連する状態は、その機能のディレクトリ内に配置します。
例: features/cart/ ディレクトリ
/src/features/cart/├── components/│ └── CartDisplay.tsx├── store/ // 状態管理コードを配置│ ├── cartSlice.ts│ └── cartSelector.ts└── hooks/ └── useCart.tsグローバルな状態管理
Section titled “グローバルな状態管理”アプリケーション全体で共有される、どの機能にも属さない状態(例:テーマ設定、言語設定)は、src/storeのようなルートレベルのディレクトリに配置します。
例: src/store/ ディレクトリ
/src/store/├── theme/│ └── themeSlice.ts├── user/│ └── userSlice.ts└── index.ts4. まとめ:なぜこの構成が優れているのか? 💡
Section titled “4. まとめ:なぜこの構成が優れているのか? 💡”このアプローチは、以下の点でアプリケーションの品質を向上させます。
-
責務の分離: UI、ビジネスロジック、状態管理、データアクセスが明確に分離されます。これにより、開発者は関心のあるレイヤーに集中できます。
-
再利用性: 状態管理のロジックはUIから独立しているため、異なるUIコンポーネントから同じ状態管理ロジックを再利用できます。
-
テストの容易性: 各レイヤーが独立しているため、UIやデータ層に依存することなく、状態管理ロジックを単独でテストできます。
-
スケーラビリティ: 新しい機能を追加する際、既存の機能に影響を与えることなく、新しい
featuresディレクトリと関連する状態管理コードを追加できます。
アーキテクチャパターンの選択判断
Section titled “アーキテクチャパターンの選択判断”機能ベース構成 vs レイヤーベース構成
Section titled “機能ベース構成 vs レイヤーベース構成”機能ベース構成(Feature-Based):
/features /auth /users /orders /productsメリット:
- 関連するコードが一箇所に集約
- 機能の追加・削除が容易
- チーム間の責務が明確
デメリット:
- 共通コードの重複のリスク
- 横断的関心事(認証、ロギング)の扱いが難しい
レイヤーベース構成(Layer-Based):
/components/hooks/utils/apiメリット:
- 共通コードの再利用が容易
- 横断的関心事の管理が簡単
デメリット:
- 関連するコードが散在
- 機能の全体像が把握しにくい
推奨: ハイブリッド構成
/src /features # 機能ベース(主要な機能) /auth /users /orders /components # レイヤーベース(共通UI) /ui /layout /lib # レイヤーベース(インフラ) /api /db /utilsスケーラビリティの考慮
Section titled “スケーラビリティの考慮”小規模プロジェクト(1-3人、< 50コンポーネント):
/app/components/lib中規模プロジェクト(4-10人、50-200コンポーネント):
/app/src /features /components /lib大規模プロジェクト(10+人、200+コンポーネント):
/app/src /features /auth /components /hooks /api /store /shared /components /hooks /utils /lib実践的な設計判断
Section titled “実践的な設計判断”コンポーネントの配置判断
Section titled “コンポーネントの配置判断”判断基準:
// 1. 機能固有のコンポーネント → features配下// - ユーザー機能にのみ使用される
// 2. 複数の機能で使用される → shared/components配下// shared/components/ui/Button.tsx// - アプリケーション全体で使用される
// 3. レイアウト関連 → components/layout配下// components/layout/Header.tsx// - レイアウト構造に関連する状態管理の配置判断
Section titled “状態管理の配置判断”判断基準:
// 1. 機能固有の状態 → features配下// - カート機能にのみ関連
// 2. グローバルな状態 → store配下// store/theme/themeSlice.ts// - アプリケーション全体で使用
// 3. サーバー状態 → features配下のapi// features/users/api/userApi.ts// - React QueryやSWRで管理このようなフォルダ構成は、初期のセットアップに少し時間がかかりますが、長期的に見ると、チーム開発や大規模なプロジェクトにおいて、コードの管理と保守を大幅に簡素化します。
シニアエンジニアとして考慮すべき点:
- プロジェクトの規模: 小規模ならシンプルに、大規模なら構造化
- チームの構成: 機能チームなら機能ベース、専門チームならレイヤーベース
- 将来の拡張性: プロジェクトの成長を見据えた設計
- 一貫性: チーム全体で統一された構成を維持