プロジェクト構造とフォルダ編成¶
はじめに¶
適切に編成されたフォルダ構造は、保守性、スケーラビリティ、開発者体験にとって極めて重要です。このガイドでは、Clean Architecture、DDDの概念、Atomic Design原則を組み合わせた最新のNext.jsフォルダ構造について解説します。
核となる原則¶
1. 機能ベースの編成¶
技術的タイプではなく、機能/ドメインでファイルをグループ化します。
❌ 悪い例(技術的グループ化):
src/
├── components/
│ ├── UserCard.tsx
│ ├── ProductCard.tsx
│ └── OrderCard.tsx
├── hooks/
│ ├── useUsers.ts
│ ├── useProducts.ts
│ └── useOrders.ts
└── services/
├── userService.ts
├── productService.ts
└── orderService.ts
✅ 良い例(機能ベース):
src/
├── features/
│ ├── users/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ ├── products/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ └── orders/
│ ├── components/
│ ├── hooks/
│ └── services/
利点: - 関連するコードを見つけやすい - より良いコード所有権 - 機能の削除が容易 - より明確な境界
2. コロケーション¶
ファイルを使用される場所の近くに保持します。
// ✅ 良い例: コンポーネントとスタイルを一緒に
components/
├── ProductCard/
│ ├── ProductCard.tsx
│ ├── ProductCard.module.css
│ ├── ProductCard.test.tsx
│ └── index.ts
// ❌ 悪い例: フォルダ間に散在
components/
├── ProductCard.tsx
styles/
├── ProductCard.module.css
tests/
├── ProductCard.test.tsx
3. 明確な命名規則¶
| タイプ | 規則 | 例 |
|---|---|---|
| コンポーネント | PascalCase | UserProfile.tsx |
| Hooks | camelCase + useプレフィックス |
useUserData.ts |
| ユーティリティ | camelCase | formatDate.ts |
| 型 | PascalCase + Typeサフィックス |
UserType.ts |
| 定数 | UPPER_SNAKE_CASE | API_ENDPOINTS.ts |
| Context | PascalCase + Context |
ThemeContext.tsx |
Next.js App Router構造¶
基本App構造¶
my-app/
├── app/ # Next.js App Router
│ ├── (auth)/ # ルートグループ(URLセグメントなしのレイアウト)
│ │ ├── login/
│ │ │ └── page.tsx
│ │ ├── register/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (main)/ # 別のルートグループ
│ │ ├── dashboard/
│ │ │ └── page.tsx
│ │ ├── products/
│ │ │ ├── [id]/
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── api/ # APIルート
│ │ ├── products/
│ │ │ └── route.ts
│ │ └── users/
│ │ └── route.ts
│ ├── layout.tsx # ルートレイアウト
│ ├── page.tsx # ホームページ
│ ├── error.tsx # エラーバウンダリ
│ ├── loading.tsx # ローディングUI
│ └── not-found.tsx # 404ページ
├── src/ # オプション: ソースディレクトリ
│ ├── features/ # 機能モジュール
│ ├── shared/ # 共有コード
│ └── core/ # コアビジネスロジック
├── public/ # 静的アセット
├── tests/ # テストファイル
└── 設定ファイル...
ルートグループ¶
括弧()を使用してURL構造に影響を与えずにルートを整理:
app/
├── (marketing)/
│ ├── about/page.tsx → /about
│ ├── contact/page.tsx → /contact
│ └── layout.tsx (マーケティングレイアウト)
├── (shop)/
│ ├── products/page.tsx → /products
│ ├── cart/page.tsx → /cart
│ └── layout.tsx (ショップレイアウト)
└── layout.tsx (ルートレイアウト)
Clean Architecture構造¶
完全な構造¶
src/
├── app/ # Next.js App Router(インフラストラクチャ)
│ ├── (auth)/
│ ├── (main)/
│ ├── api/
│ └── layout.tsx
│
├── core/ # ビジネスロジック(フレームワーク非依存)
│ ├── domain/ # エンティティと値オブジェクト
│ │ ├── entities/
│ │ │ ├── User.ts
│ │ │ ├── Product.ts
│ │ │ └── Order.ts
│ │ ├── valueObjects/
│ │ │ ├── Email.ts
│ │ │ ├── Price.ts
│ │ │ └── Address.ts
│ │ └── services/
│ │ └── PriceCalculationService.ts
│ │
│ ├── usecases/ # アプリケーションロジック
│ │ ├── users/
│ │ │ ├── RegisterUser.ts
│ │ │ └── GetUserProfile.ts
│ │ ├── products/
│ │ │ ├── CreateProduct.ts
│ │ │ └── GetProducts.ts
│ │ └── interfaces/ # Use Caseインターフェース
│ │ ├── repositories/
│ │ └── services/
│ │
│ └── common/ # 共有ドメインコード
│ ├── errors/
│ ├── types/
│ └── utils/
│
├── infrastructure/ # 外部システムとフレームワーク
│ ├── repositories/
│ │ ├── ApiUserRepository.ts
│ │ ├── ApiProductRepository.ts
│ │ └── LocalStorageRepository.ts
│ │
│ ├── services/
│ │ ├── EmailService.ts
│ │ ├── AuthService.ts
│ │ └── PaymentService.ts
│ │
│ ├── api/
│ │ ├── client.ts
│ │ └── endpoints.ts
│ │
│ └── ui/ # React/Next.js固有
│ ├── components/
│ ├── hooks/
│ ├── actions/ # Server Actions
│ └── providers/
│
├── shared/ # 機能間で共有
│ ├── ui/ # UIコンポーネント(Atomic Design)
│ │ ├── atoms/
│ │ │ ├── Button/
│ │ │ ├── Input/
│ │ │ └── Icon/
│ │ ├── molecules/
│ │ │ ├── FormField/
│ │ │ └── Card/
│ │ ├── organisms/
│ │ │ ├── Header/
│ │ │ ├── Footer/
│ │ │ └── Sidebar/
│ │ └── templates/
│ │ └── PageLayout/
│ │
│ ├── hooks/ # 共有Hooks
│ │ ├── useDebounce.ts
│ │ └── useMediaQuery.ts
│ │
│ ├── utils/ # ユーティリティ関数
│ │ ├── format.ts
│ │ └── validation.ts
│ │
│ └── types/ # 共有型
│ └── common.ts
│
├── config/ # 設定
│ ├── dependencies.ts # Dependency Injection
│ ├── constants.ts
│ └── env.ts
│
└── styles/ # グローバルスタイル
├── globals.css
└── theme.css
機能ベース構造¶
機能モジュールパターン¶
features/
├── auth/
│ ├── domain/
│ │ ├── entities/
│ │ │ └── User.ts
│ │ └── valueObjects/
│ │ └── Password.ts
│ ├── application/
│ │ ├── usecases/
│ │ │ ├── LoginUser.ts
│ │ │ └── RegisterUser.ts
│ │ └── interfaces/
│ │ └── AuthRepository.ts
│ ├── infrastructure/
│ │ ├── repositories/
│ │ │ └── ApiAuthRepository.ts
│ │ └── services/
│ │ └── JwtService.ts
│ └── presentation/
│ ├── components/
│ │ ├── LoginForm.tsx
│ │ └── RegisterForm.tsx
│ ├── hooks/
│ │ └── useAuth.ts
│ └── actions/
│ └── authActions.ts
│
├── products/
│ ├── domain/
│ ├── application/
│ ├── infrastructure/
│ └── presentation/
│
└── orders/
├── domain/
├── application/
├── infrastructure/
└── presentation/
各機能は以下で自己完結: - ドメインロジック(エンティティ、値オブジェクト) - Use cases(アプリケーションロジック) - インフラストラクチャ(APIクライアント、リポジトリ) - プレゼンテーション(UIコンポーネント、hooks)
Atomic Design構造¶
コンポーネント編成¶
shared/ui/
├── atoms/ # 最小のコンポーネント
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.module.css
│ │ ├── Button.test.tsx
│ │ ├── Button.stories.tsx # Storybook
│ │ └── index.ts
│ ├── Input/
│ ├── Text/
│ └── Icon/
│
├── molecules/ # Atomsの組み合わせ
│ ├── FormField/
│ │ ├── FormField.tsx
│ │ └── index.ts
│ ├── SearchBox/
│ └── Card/
│
├── organisms/ # 複雑なコンポーネント
│ ├── Header/
│ │ ├── Header.tsx
│ │ ├── Header.test.tsx
│ │ └── index.ts
│ ├── ProductCard/
│ └── Navigation/
│
└── templates/ # ページレイアウト
├── DashboardLayout/
├── AuthLayout/
└── MainLayout/
Atomic Design階層:
- Atoms: Button、Input、Icon、Label
- Molecules: FormField(Label + Input + Error)、SearchBox(Input + Button)
- Organisms: Header(Logo + Nav + SearchBox)、ProductCard
- Templates: Organismsを構成するページレイアウト
- Pages: app/ディレクトリ内の実際のルート
Barrel Exports (index.ts)¶
Barrel filesを使用してimportを簡素化:
// features/products/index.ts
export * from './domain/entities/Product';
export * from './application/usecases/CreateProduct';
export * from './presentation/components/ProductCard';
export { useProducts } from './presentation/hooks/useProducts';
// 使用方法
import { Product, CreateProduct, ProductCard, useProducts } from '@/features/products';
利点: - よりクリーンなimport - 内部構造を隠蔽 - リファクタリングが容易
注意点: - 循環依存の原因となる可能性 - 注意しないとバンドルサイズが増加 - パブリックAPIにのみ控えめに使用
パスエイリアス¶
tsconfig.jsonで設定:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/app/*": ["./src/app/*"],
"@/features/*": ["./src/features/*"],
"@/shared/*": ["./src/shared/*"],
"@/core/*": ["./src/core/*"],
"@/config/*": ["./src/config/*"]
}
}
}
使用方法:
// これの代わりに: import { Button } from '../../../shared/ui/atoms/Button'
import { Button } from '@/shared/ui/atoms/Button';
// これの代わりに: import { User } from '../../core/domain/entities/User'
import { User } from '@/core/domain/entities/User';
ファイル命名規則¶
コンポーネント¶
✅ 良い例:
components/
├── ProductCard.tsx (PascalCase)
├── UserProfile.tsx
└── ShoppingCart.tsx
❌ 悪い例:
components/
├── product-card.tsx
├── user_profile.tsx
└── shoppingcart.tsx
Hooks¶
✅ 良い例:
hooks/
├── useAuth.ts (camelCase + 'use'プレフィックス)
├── useProducts.ts
└── useDebounce.ts
❌ 悪い例:
hooks/
├── Auth.ts
├── ProductsHook.ts
└── use-debounce.ts
ユーティリティ¶
✅ 良い例:
utils/
├── formatDate.ts (camelCase)
├── validation.ts
└── string.ts
❌ 悪い例:
utils/
├── FormatDate.ts
├── VALIDATION.ts
└── String.ts
目的による命名¶
ドメイン層¶
domain/
├── entities/
│ ├── User.ts # ビジネスエンティティ
│ ├── Product.ts
│ └── Order.ts
├── valueObjects/
│ ├── Email.ts # 不変の値
│ ├── Price.ts
│ └── Address.ts
└── services/
└── OrderPricingService.ts # ドメインロジック
Use Cases¶
usecases/
├── users/
│ ├── RegisterUser.ts # コマンド(状態を変更)
│ ├── GetUserProfile.ts # クエリ(状態を読み取り)
│ └── UpdateUserProfile.ts # コマンド
└── products/
├── CreateProduct.ts
├── GetProducts.ts
└── DeleteProduct.ts
コンポーネント¶
components/
├── ProductCard.tsx # UIコンポーネント
├── UserAvatar.tsx
└── forms/
├── LoginForm.tsx # フォームコンポーネント
└── RegisterForm.tsx
Server vs Client Component命名¶
オプション1: ディレクトリベース¶
components/
├── server/ # Server Components
│ ├── ProductList.tsx
│ └── UserProfile.tsx
└── client/ # Client Components
├── AddToCartButton.tsx
└── SearchBox.tsx
オプション2: サフィックスベース¶
components/
├── ProductList.server.tsx # Server Component
├── UserProfile.server.tsx
├── AddToCartButton.client.tsx # Client Component
└── SearchBox.client.tsx
オプション3: 暗黙的(推奨)¶
components/
├── ProductList.tsx # デフォルトでServer(App Router)
└── AddToCartButton.tsx # 先頭に'use client'
ほとんどのチームはオプション3を使用 - App RouterではコンポーネントはデフォルトでServer components、Client componentsは明示的に'use client'を宣言します。
フォルダ構造の例¶
小規模プロジェクト(スタートアップ/MVP)¶
my-app/
├── app/
│ ├── (marketing)/
│ ├── dashboard/
│ ├── api/
│ └── layout.tsx
├── components/
│ ├── ui/ # 共有UI
│ └── features/ # 機能コンポーネント
├── lib/
│ ├── api.ts
│ └── utils.ts
├── hooks/
├── types/
└── config/
迅速なイテレーションのためのシンプルでフラットな構造。
中規模プロジェクト(成長中のプロダクト)¶
my-app/
├── app/
├── src/
│ ├── features/ # 機能モジュール
│ │ ├── auth/
│ │ ├── products/
│ │ └── orders/
│ ├── shared/
│ │ ├── ui/ # Atomic Design
│ │ ├── hooks/
│ │ └── utils/
│ ├── core/ # ビジネスロジック
│ │ ├── domain/
│ │ └── usecases/
│ └── infrastructure/
│ ├── api/
│ └── repositories/
├── config/
└── tests/
軽量のClean Architectureを持つ機能ベース。
大規模プロジェクト(エンタープライズ)¶
my-app/
├── app/
├── src/
│ ├── features/ # 境界づけられたコンテキスト(DDD)
│ │ ├── identity/ # 認証とユーザー
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ ├── infrastructure/
│ │ │ └── presentation/
│ │ ├── catalog/ # 製品
│ │ ├── ordering/ # 注文
│ │ └── payment/ # 支払い
│ ├── shared/
│ │ ├── kernel/ # 共有ドメイン
│ │ ├── ui/
│ │ └── utils/
│ └── core/
│ ├── domain/
│ ├── usecases/
│ └── interfaces/
├── infrastructure/
│ ├── persistence/
│ ├── api/
│ └── messaging/
├── config/
│ └── dependencies/ # DIコンテナ
└── tests/
├── unit/
├── integration/
└── e2e/
境界づけられたコンテキストとClean Architectureを持つ完全なDDD。
ベストプラクティス¶
すべきこと¶
✅ タイプではなく機能でグループ化
✅ 関連ファイルを一緒に保持
✅ 明確で説明的な名前を使用
✅ ドメイン境界で整理
してはいけないこと¶
❌ 深くネストしすぎない
// 悪い例
src/features/products/components/cards/product/ProductCard.tsx
// 良い例
src/features/products/components/ProductCard.tsx
❌ 関心事を混在させない
// 悪い例: UIとドメインロジックが混在
components/User.tsx (検証、API呼び出し、UIを持つ)
// 良い例: 分離
domain/User.ts (検証)
hooks/useUser.ts (API呼び出し)
components/UserCard.tsx (UI)
❌ 一般的な名前を使用しない
移行戦略¶
フラットから機能ベースへ¶
ステップ1: 機能を特定
ステップ2: 機能フォルダを作成
features/
├── users/
│ └── components/
│ └── UserCard.tsx
├── products/
│ └── components/
│ └── ProductCard.tsx
└── orders/
└── components/
└── OrderCard.tsx
ステップ3: 関連ファイルを移動
features/users/
├── components/
│ └── UserCard.tsx
├── hooks/
│ └── useUsers.ts
└── api/
└── usersApi.ts
ステップ4: importを更新
// 旧
import { UserCard } from '@/components/UserCard';
// 新
import { UserCard } from '@/features/users';
VS Code拡張機能¶
構造を維持するのに役立つ拡張機能:
- Folder Icons:
vscode-iconsまたはmaterial-icon-theme - Path Autocomplete:
Path Intellisense - Import Cost: importのサイズを表示
- Barrel: index.tsファイルを自動生成
リソース¶
まとめ¶
推奨構造(2025年):
src/
├── app/ # Next.jsルーティングとページ
├── features/ # 機能モジュール(ドメイン駆動)
├── shared/ # 共有UIとユーティリティ
├── core/ # ビジネスロジック(Clean Architecture)
├── infrastructure/# 外部システム
└── config/ # アプリ設定
重要な原則: - 機能ベースの編成 - 関連ファイルのコロケーション - 明確な命名規則 - 適切な関心の分離 - スケーラブルで保守可能
以下に基づいて構造を選択: - チームサイズ - プロジェクトの複雑さ - ビジネスドメインの複雑さ - 長期的な保守ニーズ