UIツリーで考えるNextJS AppRouterディレクトリ構成
はじめに
Reactで開発をする中で常に課題となるのがディレクトリ構成かと思います。
いくつかプロジェクトを経験して、今のところBetterかなと思われるディレクトリ構成を紹介したいと思います。
この記事では、今まであまり考慮されていなかった印象のある、UIツリーに関心を持ち、 UIツリー構造とUIコンポーネント群を分けて考えるディレクトリ構成を提案したいと思います。
ディレクトリ構成
NextJS AppRouter
/app
|-- _packages
| |-- _features
| |-- ${domain}
| |-- types
| |-- libs
| |-- components
| |-- hooks
| |-- zodSchemas
| |-- components
| |-- icons
| |-- libs
| |-- hooks
| |-- types
|-- sub-tree
| |-- page.tsx # Parent Node
| |-- ClientComponent.tsx # UpperCamel / Client Component Wrapper(import package features/domain)
| |-- _server
| |-- ServerComponent.tsx # UpperCamel / Server Component
コンセプト
今までReactで開発する中で突き当たる問題が、Pathが異なる箇所で同じコンポーネントが使われる、といったケースでした。
NextJSではコロケーションを前提としたディレクトリ構成が紹介されていますが、実際に開発する中でルーティングとコンポーネントを対応させると、別のルーティングで同じコンポーネントを参照したい場合に不便です。
なので、そもそも、ルーティングとコンポーネントの構造は分けて考えるべきかなと思ってます。
察しの良い方はお気づきかもしれませんが
- UIツリー構造:sub-tree
- 機能ドメインと共通コンポーネント群:_packages配下
といった構造が見て取れるかと思います。
NextJSの公式ドキュメントにも見て取れるように、UIツリーを構築するといったメンタルモデルは、app routerと相性が良いと思います。
ただ、UIツリー構造の異なる箇所で同じコンポーネントを参照したいといったケースは現実にはよくあるパターンです。
ならばそれらを分けて考えようというのが、今回紹介したいディレクトリ構成パターンの根幹となります。
フロントエンドはUIツリー構造とコンポーネントに分かれる
ここまでで明らかに基準として設けられるのは、2つの判断軸です。
UIツリー構造とコンポーネントになります。
また、コンポーネントも共通コンポーネントと機能ドメインコンポーネントに分けられます。
上記のディレクトリ構成図に基づくと、
_packages直下が共通コンポーネントに関わる部分になります。
そして、_packages/featuresが機能ドメイン単位のコンポーネントを定義する箇所となります。
packages配下は基本的にmisukenさんが紹介してくれているアンチパターンを理解して package by feature への記事が詳しいです。
Reactベストプラクティスの宝庫!「bulletproof-react」が勉強になりすぎる件にも記載されているように、featuresディレクトリを切るのは、もはやデファクトスタンダードかと思うので、この部分についての新規性はないと思いますが、UIツリー構造を関心として分けるといった点について明言されているものは見かけた記憶がないので、あえて今回記事にしてみました。
さいごに
Reactの好きなところでもあり、また難しいところでもあると思うのですが、Metaのプロダクトは基本的に開発者が自ら考え判断することを推奨しているように思える文章が多いです。※
経験的な判断と一方で起こりうる性質に基づいた演繹的な判断を強く求められるライブラリであると思います。
また、一つの機能を作る、といった観点で考えても、コンポーネント、関数、カスタムHooks、型定義が最低でも関連してきますし、FormValidationにZodを使っていたり、RHFを使っていればその分ファイルは細分化されていきます。
ReactはGoogleのプロダクトであるFlutterやAnglarのような、すでに標本があってそれを真似れば良い、といった毛色のものではなく、各々の状況や使用している技術に依存して、論理的に考える能力を求められる技術であるように思います。
この記事がディレクトリ構成を考える一つの判断基準となれば幸いです。
Discussion