📁

小さくはじめる Next.js App Router ディレクトリ構成 ~featuresでコンポーネントを整理する~

2024/04/27に公開

はじめに

最近自分がNext.jsの環境構築、設計を担当する機会が増えました。
ディレクトリ構成が固まってきたので一つの案として参考になれば幸いです。

こんな方におすすめ!

  • Next.js App Routerを使って小規模開発(個人開発含む)を行いたい方
  • Next.js App Routerに関してなんとなく理解したけどディレクトリ構成で迷ってる方
  • featuresになんとなく興味のある方

目次

featuresとは何か

アプリケーション内で独立した機能として識別できる要素のこと。
中にはUIコンポーネント、ロジック、スタイルなどが入る。
1行でまとめるのであれば
ロジックのあるコンポーネントはfeaturesを見れば大体把握できる
という認識かなと思います。

構成案

以下構成はsrcディレクトリ内と想定します。
srcを使わない場合はルートディレクトリに記述するものとします。

ディレクトリ構成
├── app          ... ルーティングに関するコンポーネント
├── features     ... ロジック + コンポーネントをまとめたもの
│   ├── common   ... 共通部分
│   └── routes   ... 特定のページで使うもの
├── components   ... ロジックがない共通コンポーンネント
├── hooks        ... 共通ロジックの内、React Hooksが「ある」もの
├── utils        ... 共通ロジックの内、React Hooksが「ない」もの
└── constants    ... 定数を定義したファイル

※treeは説明順に記載してます

appディレクトリ

ルートページに関することを記述。
appディレクトリでは"use client"を記述しない方向性。
つまりuse〇〇といったhooksは使わないのでServer Componentだけになります。

appディレクトリに記述するもの

  • Server Sideでデータをfetchするコンポーネント
  • ロジックを含まない見た目だけを実装するコンポーネント。以下例
    • テキスト
    • 見た目だけのDOM
    • リンク
appディレクトリサンプル(ブログサイトを想定)
├── blog
│   ├── [uuid]
│   │   └── page.tsx
│   │       └── edit
│   │           └── page.tsx
│   ├── create
│   │   └── page.tsx
│   └── page.tsx
├── login
│   └── page.tsx
├── profile
│   └── page.tsx

featuresディレクトリ

今回の目玉とも言えるディレクトリ。
ロジック+コンポーネントのファイルは全てfeaturesディレクトリの中に記述します。
基本的にはappディレクトリから呼び出されることになります。

featuresディレクトリサンプル(ブログサイトを想定)
├── common
│   ├── editors
│   │   ├── components
│   │   │   ├── CodeEditor.tsx
│   │   │   └── RichTextEditor.tsx
│   │   ├── hooks.ts
│   │   └── stores.ts
│   ├── forms
│   │   ├── components
│   │   │   ├── Input.tsx
│   │   │   └── Select.tsx
│   │   ├── hooks.ts
│   │   └── stores.ts
│   └── tags
│       ├── components
│       │   ├── TagItem.tsx
│       │   └── TagList.tsx
│       ├── hooks.ts
│       └── stores.ts
└── routes
    ├── auth
    │   ├── components
    │   │   └── Login.tsx
    │   ├── endpoint.ts
    │   └── hooks.ts
    ├── post
    │   ├── detail
    │   │   ├── components
    │   │   │   ├── Contents.tsx
    │   │   │   └── Suggests.tsx
    │   │   ├── endpoint.ts
    │   │   ├── hooks.ts
    │   │   └── stores.ts
    │   └── list
    │       ├── components
    │       │   ├── List.tsx
    │       │   ├── Search.tsx
    │       │   └── Tabs.tsx
    │       ├── endpoint.ts    
    │       ├── hooks.ts
    │       └── stores.ts
    └── profile
        ├── components
        │   ├── Email.tsx
        │   └── Password.tsx
        ├── endpoint.ts
        ├── hooks.ts
        └── stores.ts

features/commonディレクトリ

複数のページで使われるロジック込みの共通コンポーネント。
主に以下のものになります(ブログサイトを想定した場合)

  • フォーム
  • エディタ
  • タグ
    など

features/routesディレクトリ

1つのページ種類で使われるロジック込みの非・共通コンポーネント。
主に以下のものになります(ブログサイトを想定した場合)

  • ログイン
  • 投稿一覧
  • 投稿詳細
  • 投稿画面
  • プロフィール
    など

features内のディレクトリ・ファイルについて

componentsディレクトリ

見た目部分をまとめたディレクトリ
ロジックに関してはhooks.tsに記述する

endpoint.tsファイル

APIを実行エンドポイントを記載。
ここには基本的にfetchやaxiosといったものだけを記載する

hooks.tsファイル

コンポーネントの内、ロジックを記載

stores.tsファイル

ファイル間を跨いだstateを管理。
1ファイル内で完結するstate管理のみuseStateを使用する。

componentsディレクトリ

ロジックがない共通コンポーンネント。
あらゆるページで使用できるUIに関することを記述します

componentsディレクトリサンプル
├── Alert.tsx
├── Button.tsx
├── Label.tsx
├── Logo.tsx
├── Menu.tsx
└── Modal.tsx

hooksディレクトリ

共通ロジックの内、React Hooksが「ある」ものを記述。
ここには共通ロジックしか記述しないので、特定のページ群(ブログサイトであれば投稿関連画面など)でしか使わないReact Hooksに関してはfeaturesディレクトリに記述します。
React Hooksの説明は公式ドキュメントがわかりやすいです。

https://ja.react.dev/reference/react/hooks

hooksディレクトリサンプル
├── useRedirect.ts
├── useSave.ts
├── useSearch.ts
├── useToast.ts
└── useWidthSize.ts

utilsディレクトリ

共通ロジックの内、React Hooksが「ないもの」ものを記述。
例えば特定の文字を整形して変換したり、特定の値を日本語に変換するなど。
記述の記述場所に困った時は大体ここに書くことが多そうです。

utilsディレクトリサンプル
├── dates.ts
├── formats.ts
└── validation.ts

constantsディレクトリ

定数を定義したファイル。
colorコードや都道府県selectBoxなどを管理することが多そうです。

constantsディレクトリサンプル
├── options.ts
├── paths.ts
└── styles.ts

実際に実装してみて

方針としてはルールはあまり決めずに設置場所は開発者に任せようぜ!というスタンスかなと思います。
迷わないというメリットはありつつ、ルールがないからこそ、開発者によって認識齟齬が発生するというデメリットもあるかなと思います。例えばどこからcommon(共通)になるのか?、同じendpointを複数ページで実行する場合どこに記述するか? などです。
スモールスタートでNext.jsを使う場合はとにかく手を動かしてトライアンドエラーを繰り返すのがいいかなと思います。
最後までお読みいただきありがとうございます!

Discussion