コンテンツにスキップ

プロジェクト構造とフォルダ編成

はじめに

適切に編成されたフォルダ構造は、保守性、スケーラビリティ、開発者体験にとって極めて重要です。このガイドでは、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。

ベストプラクティス

すべきこと

タイプではなく機能でグループ化

features/products/  ✅
components/         ❌ (機能の場合)

関連ファイルを一緒に保持

Button/
├── Button.tsx
├── Button.test.tsx
└── Button.module.css

明確で説明的な名前を使用

useUserAuthentication.ts  ✅
useAuth.ts               ✅
auth.ts                  ❌

ドメイン境界で整理

features/
├── auth/      (アイデンティティ)
├── products/  (カタログ)
└── orders/    (注文)

してはいけないこと

深くネストしすぎない

// 悪い例
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)

一般的な名前を使用しない

utils/helpers.ts     ❌ (一般的すぎる)
utils/dateFormat.ts  ✅ (具体的)

移行戦略

フラットから機能ベースへ

ステップ1: 機能を特定

現在:
components/
├── UserCard.tsx
├── ProductCard.tsx
├── OrderCard.tsx
...

ステップ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/        # アプリ設定

重要な原則: - 機能ベースの編成 - 関連ファイルのコロケーション - 明確な命名規則 - 適切な関心の分離 - スケーラブルで保守可能

以下に基づいて構造を選択: - チームサイズ - プロジェクトの複雑さ - ビジネスドメインの複雑さ - 長期的な保守ニーズ