💪

ぼくがかんがえた最強の React, Next.js ディレクトリ構成

に公開

React doesn’t have opinions on how you put files into folders.[1]
( React はディレクトリ構成に意見を持ちません )

Laravel[2]など一部言語やフレームワークでは推奨ディレクトリ構成が示されている一方、
このように、React は公式でディレクトリ構成に意見を持たないと明言しています。

そんな中 ゆめみパスポート の課題に取り組むにあたって設計やディレクトリ構成について調べたので、共有したいと思います。

この記事は以下の記事に強く影響を受けています。
詳しくディレクトリ構成について知りたい方は以下の記事を確認してみてください。
https://zenn.dev/bln/articles/986b709f4df0c1#フォルダ構成の考え方

結論

src
├── app
│   ├── _components       # ページ固有のコンポーネント
│   └── page.tsx
├── components            # 汎用コンポーネント
│   ├── buttons
│   └── modals
├── contexts
├── features              # 機能単位のディレクトリ
│   ├── prefecture        # 都道府県(機能)に関するコード
│   │   ├── components
│   │   └── schemas
│   └── population
│       └── ...
└── lib

このようなトップレベルでは feature-based、各 feature ディレクトリ内では type-based なディレクトリ構成を採用することで、
適切にコロケーションを行いつつ、各 feature ディレクトリ内では技術的な関心の分離によって、ロジックとUIが分離されテストしやすい構成になります。
(各用語は後述)

〇〇-based

ディレクトリ構成において何に基づいてディレクトリを分けるかを表す言葉として「〇〇-based」という言葉が使われます。
ほかにも 「package by 〇〇」と呼ばれることもあります。

feature-based

機能単位でのディレクトリ構成を指します。
例えば features/prefecture, features/population などが該当します。
これは ビジネスドメイン などと同じような考え方です。

type-based

機能単位でのディレクトリ構成を指します。
例えば create-next-app で生成される public, app, components などが該当します。
また、Laravel[3]などで推奨されている MVC における Models, Views, Controllers などもそれにあたります。

layer-based

レイヤー単位でのディレクトリ構成を指します。
レイヤードアーキテクチャーのような層に基づいてコードを分割するディレクトリ構成で、
アプリケーション、ドメイン、インフラのような層に基づいたディレクトリが該当します。

コロケーション(co-location)について

コロケーションとは 関連度の高いコードは近くに置こう という考え方です。
また、コロケーションとは、一緒に変化するものは近くに置こう という考え方だと言い換えることができます。

コード変更の一般的なシナリオでは prefecture のスキーマだけを変更するということは考えづらく、スキーマを変更するときUIなどほかの関連する部分も変更するはずです。

具体的には、技術的な関心でディレクトリを分けていると以下のような問題が生じます:

  1. 変更の時いろいろなディレクトリを行ったり来たりしないといけない
  2. コードリーディングの時に保持しなければいけないコンテキストが増える

1 は自明だと思いますが、2 に関してはある機能がどのように動作しているか読み解く場合、
トップレベルで type-based なディレクトリ構成になっていると src 配下のすべてのディレクトリに対して、対象の機能に関するコードがどこにあるかという情報を保持する必要があります。
しかし、feature-based なディレクトリ構成にすることで、対象の機能に関するコードは features/hoge に集合しており、それ以下のディレクトリを探索するだけで済みます。

このように規模が大きくなるほどにコロケーションのメリットも大きくなると思います。

ディレクトリ構成のネスト

ここまででなんとなく feature-based がよさそうと思ってきたと思います。
しかし、feature-based なディレクトリ構成には一つ欠点があります。
機能に関するコードは一つのディレクトリにフラットに配置するには膨大だということです。

そこで、feature-based な各ディレクトリ配下に type-based または layer-based なディレクトリを構築します。
これにより、コロケーションのメリットを享受しつつ、技術的な関心によってコードを分離することで、UIとロジックが分離され テスト可能になります。

今回は feature-based, type-based のネストを採用しましたが、規模の大きいアプリケーション、複雑なロジックをもつアプリケーションでは feature-based, layer-based のネストも選択肢だと思います。

まとめ

  • コロケーションが大事
  • 読み解きやすいコードベースに保つには feature-based がよさそう
  • 書きやすいコードベースには type-based, layer-based がよさそう
  • 両方のメリットを享受するためにネストの選択肢がある

[余談] テストファイルの配置に関して

src/hoge.ts のテストを書く場合、tests/unit/hoge.ts と書く派閥と src/hoge.test.ts と書く派閥があると思います。
これもコロケーションの観点から後者のようにテスト対象のコードと同じ階層に配置するべきだと思います。
そして e2e テストなどすべてのコードを横断するようなテストコードは e2e/ などに配置します。

参考文献

https://zenn.dev/pandanoir/articles/d74d317f2b3caf
https://zenn.dev/bln/articles/986b709f4df0c1

脚注
  1. https://legacy.reactjs.org/docs/faq-structure.html#is-there-a-recommended-way-to-structure-react-projects ↩︎

  2. https://laravel.com/docs/12.x/structure ↩︎

  3. https://laravel.com/docs/12.x/structure ↩︎

GitHubで編集を提案

Discussion