Next.js App Router 私の私による私のためのディレクトリ構成を考える
はじめに
Next.js App Routerは、いまだWeb上にディレクトリ構成についての情報が少ないように思っているし、私はディレクトリ構成をうまくできていない。
それぞれのプロジェクトや個人開発においてオレオレアーキテクチャ、オレオレディレクトリ構成を利用していると信じてやまない。(実際のところは不明ですが)
これが、私の考えた!! Next.js App Router ディレクトリ構成だ!! 勝負しようぜ!!
(//FIXME 何を? 誰と? 私のと書いてあるけれど、他の人の記事を参考にしまくっていますよね?)
上記は冗談として、誰とも勝負するつもりはありませんので。これは私のためのディレクトリ構成です。
主な採用技術
・Next.js 14 App Router
・TypeScript
・Tailwind CSS
・Zod
・Zustand
・Prisma
・Playwright
ディレクトリ構成
全体像
├─public //静的ファイルを格納するディレクトリ
└─src
├─app //ルーティングを担うディレクトリ
│ ├─layout.tsx
│ ├─page.tsx //container.tsxを呼び出すだけのコンポーネントとする
│ ├─container.tsx //データ取得を担うコンポーネント presentational.tsxを呼び出す
│ ├─presentational.tsx //UI描画を担うコンポーネント
│ ├─author
│ │ ├─page.tsx
│ │ ├─container.tsx
│ │ └─presentational.tsx
│ └─posts
│ └─[postId]
│ ├─page.tsx
│ ├─container.tsx
│ └─presentational.tsx
│
├─features //各機能ごとでのみ利用されるコードを格納するディレクトリ
│ ├─author
│ │ ├─components
│ │ │ └─NameCard.tsx //presentational.tsxから呼び出されるコンポーネント
│ │ ├─types //型定義+Zodスキーマを格納するディレクトリ
│ │ ├─tests //単体・結合テストを格納するディレクトリ
│ │ ├─stores //グローバル状態管理定義を格納するディレクトリ
│ │ ├─hooks //React Hooksを利用しているロジックを格納するディレクトリ
│ │ ├─utils //React Hooksを利用していないロジックを格納するディレクトリ
│ │ ├─services //ビジネスロジックを格納するディレクトリ
│ │ └─repositories //DB操作を格納するディレクトリ
│ │
│ └─posts
│ ├─components
│ ├─types
│ ├─tests
│ ├─stores
│ ├─hooks
│ ├─utils
│ ├─services
│ └─repositories
│
└─shared //共通コードを格納するディレクトリ
├─components //ロジックのない共通コンポーネントを格納するディレクトリ
├─i18n //多言語機能を格納するディレクトリ
├─googleAnalytics //GA機能を格納するディレクトリ
├─types //共通型定義+共通Zodスキーマを格納するディレクトリ
├─tests //E2Eテストを格納するディレクトリ
├─constants //定数定義を格納するディレクトリ
├─libs //ライブラリ固有のコードを格納するディレクトリ
├─hooks //React Hooksを利用している共通ロジックを格納するディレクトリ
├─utils //React Hooksを利用していない共通ロジックを格納するディレクトリ
├─services //共通のビジネスロジックを格納するディレクトリ
├─repositories //共通のDB操作を格納するディレクトリ
└─prisma //Prismaのスキーマ定義を格納するディレクトリ
解説・考慮事項
フロントエンド視点
修正の影響範囲を把握しやすくするため、featuresディレクトリによるコロケーション配置をとることにした。appディレクトリにてRoute Groupsを利用してコロケーション配置をとる方法もあったが、ルーティングを担うものとして可読性を意識し独立させることにした。
データ取得とUI描画を分割したい、かつReact Server Componentsの恩恵を受けたいので、Container/Presentationalパターンをとることにした。
バックエンド視点
リポジトリパターンをまるごと取り込むとPrisma単体を扱う分には過多と考えたが、処理の隠匿、関心の分離をより厳密にやってみたく、Repository層は導入することにした。
Next.jsから外部バックエンドAPIを呼び出すのみであれば、Repository層は不要ではないかと思う。
「View」=presentational.tsxコンポーネント
「Controller」=container.tsxコンポーネント(Server Actions自体)
「Service」=services
「Repository」=repositories
「Model」=PrismaのDBスキーマ定義
終わりに
デコロケーションになっているディレクトリ構成に比べればだいぶスッキリしたし、影響範囲がわかりやすいはずだ。
ディレクトリ構成について、なんとか私がやっていけるだけの道筋が見えたように思う。
ともすると過多な部分もあるので、もっと切り詰めても良いはず。
Container/Presentationalパターンは偉大。そして、多くの先人達の記事もまた偉大。ありがとうございます。
参考文献
Discussion