Next.jsで Bulletproof-react と Service層・Repository層を取り入れたディレクトリ設計
2025-04-09
2025-04-09
11 min read
はじめに
昨今のSupabaseなどのBaaS(Backend as a Service)
+ Prisma
を用いた構成や、Next.js 14 でのServer Action
の登場により、ディレクトリ設計が複雑になっていると感じませんか?
また従来より複雑に感じる要因として、Client Components
とServer Components
をうまく統合するようにコンポーネント設計が必要であることが挙げられます。
そして設計をミスると修正するのに多大な工数がかかるし、そもそも納期などの関係で修正するタイミングも無かったりして、そのまま開発が進んでいくようなこともあると思います。
今回Next.jsにおけるディレクトリ設計やアーキテクチャーを改めて整理したので、今後の自分のためにもここにまとめておこうと思います。
前提としては
- Server Actionを利用する
- フロントエンドとバックエンドともNext.jsで開発を行う
Bulletproof-react
をベースにContainer / Presentationパターン
を意識した設計を導入するService層
とRepository層
を導入する- 小規模だとオーバーエンジニアリングになる部分もあるかもだが、スケールをしても耐えれるような構成を目指す
また現段階ではNext.js 15 、React 19 までの開発を想定しています。
構成について
全体の構成
まず全体構成としては以下のディレクトリ設計になります。
.
├── prisma # Prisma のスキーマやマイグレーションファイルを管理
├── public # 静的ファイル(画像、フォントなど)を配置
└── src/ # アプリケーションのソースコード
├── app/ # Next.js App Router のルーティングとページ
├── components/ # 再利用可能な共通の UI コンポーネント
├── constants/ # 定数や設定値を定義(例:日付フォーマット、メッセージなど)
├── features/ # ドメイン単位の機能モジュール(機能ごとのUI/ロジック)
├── hooks/ # カスタム React Hooks
├── lib/ # 外部ライブラリとの連携や共通処理(API クライアント、認証など)
├── repositories/ # データ取得・永続化処理(DBやAPIとのやり取り)
├── services/ # ビジネスロジック(use case)を実装
├── types/ # 型定義(モデル型、ユーティリティ型など)
└── utils/ # 汎用的なユーティリティ関数
アーキテクチャーはBulletproof-react
をベースに考えています。features
の中身については別途後述します。
私はReactにおけるSPA開発ではAtomic Design
を利用することが多かったのですが、昨今のNext.jsの開発における「コードの保守性と再利用」「複数人での開発」などを考えると、Bulletproof-react
の方が堅牢な設計・実装パターンを実現しやすいと感じております。
またService層
とRepository層
を導入することで、Prisma
でのDBのアクセスやAPI
を叩くロジックなどを隠蔽し、保守や再利用がを行いやすい設計になると思います。
DBのテーブルの型は「types/models」以下に「/userType.ts」のようにテーブル毎にまとめて定義しておき、全てのファイルではここの型をimportして利用するようにします。Propsの定義で必要な場合なども全てここから取り出します。
featuresディレクトリ内の構成
features
ディレクトリ内の構成は以下です。
.
└── src/features
└── domain/ # ドメイン単位:例)user/
└── feature # 機能単位:例)user-table/
├── actions/ # Server Actionの処理
├── components/ # 再利用可能なUI部品であるPresentational Component
├── constants/ # 定数
├── containers/ # 状態管理やロジックを含んだContainer Component。UIにデータやイベントを渡す役割。
├── hooks/ # カスタムフック
├── providers/ # 状態管理などのラッパーとか
├── schemata/ # Zodなどによるバリデーションスキーマやフォーム構造定義
├── types/ # 型定義
├── utils/ # 汎用的なユーティリティ関数
└── index.tsx # 機能のエントリーポイント
features
ディレクトリ内は直下に全てのfeature(機能単位)
を配置するとスケールした時に把握できなくなりそうなので、domain
で区切るようにしました。
feature
内の各ディレクトリはこういったディレクトリができるケースがあるという意味で、全て必要なわけではありません。必要なものだけ作成するイメージです。
ここで大きくポイントになるのは2つです。
Container / Presentationパターン
を意識することindex.tsx
をエントリーポイントにすること
1.Container / Presentationパターン
を意識すること
Next.jsでは
Client Components
とServer Components
が入り乱れることになるので、Container層
とPresentational層
を分けることで管理しやすくなります。
またテストが容易になったり、storybookなども使用しやすくなるメリットがあります。
例えばContainer層
ではデータの取得を行い、Presentational層
では取得したデータを受け取って表示やユーザー操作によってのアクションを提供したりします。
詳しく話すと長くなるので今回は割愛させていただき、別途記事にする予定です。
2.index.tsx
をエントリーポイントにすること
page層や別のfeatureから読み込む時はこのエントリーポイントからexportしたファイルのみimport可能にすることで、各ファイルの管理をしやくしなります。
エディターやeslintの設定でエントリーポイントからexportしていないファイルを直接読み込めばエラーが出るように設定なども可能です。
私のディレクトリ設計では以下の記述をeslint.config.mjs
に記述することで一括で適応しています。
一括ではなく個別で適応したりも可能ですが、私は毎回記述するのが大変に感じたため一括でできるように設計の方でカバーしています。
// eslint.config.mjs const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), { rules: { "no-restricted-imports": [ "error", { patterns: ["@/features/*/*/*"], // エントリーポイントをからimportしないとエラーにする }, ], }, }, ]
featuresディレクトリの構成の具体例
例えば「user-table」というfeature(機能)を作りたいとします。
すると以下のような構成になります。
.
└── src/features
└── user/
└── user-table
├── components/
│ └── UserTable
│ ├── index.tsx
│ ├── index.stories.tsx
│ └── index.test.tsx
├── containers/
│ └── UserTableContainer
│ ├── index.tsx
│ └── index.test.tsx
└── index.tsx
先ほどの述べたContainer / Presentationパターン
で設計をし、コンポーネントはディレクトリ名で表しその中にindex.tsxを配置します。
このコンポーネントに対してテストやストーリーブックのファイルを作成したい場合などを考慮してこのような構成しています。
今回は例なので簡単な内容にしていますが、機能が大きくなると各ディレクトリやファイル数は増えることが想定されます。
また余談ですが、VSCodeでindex.tsxのファイル名を開くと表示されるファイル名からでは何のファイルを開いているかがわかりにくくなってしまいます。
ですが設定で「index.tsxの場合はディレクトリ名を表示する」といったことが可能です。
私は以下の内容をsettings.jsonに記述して、この問題を回避しています。
"workbench.editor.customLabels.patterns": {
"**/index.*": "${dirname} .../${dirname(1)}",
"**/{page,layout,template,route,actions,hooks,components,utils,types}.{js,ts,jsx,tsx}": "${dirname}/${filename}.${extname} .../${dirname(1)}"
}
まとめ・補足
この設計では機能ごとにスコープを作って開発していくので、修正なども行いやすかったり、複数人での開発も行いやすいとメリットがあります。
しかし小規模なプロジェクトでもファイル数を分けて開発していく必要があるので、チーム内でルールを明確にする必要があります。
また使用するライブラリなどとの相性もあるように感じます。
私が開発を行うにあたって利用したツールやライブラリなどを列挙しておきます。
※ 技術の移り変わりが早いので、情報が古いものと入り乱れていたり、AIも追いつけていない状況が散見されます。 利用する際は公式ドキュメントを参照するのがおすすめです。 今回の設計に関係ないツールも技術選定の参考になればと思い記述しています。
Tailwind CSS v4 + shadcn/ui
:CSSフレームワークとコンポーネントライブラリconform + zod + useActionState
:フォーム周りnuqs
(旧称next-usequerystate):URLのクエリパラメータの状態管理clerk
:認証周りsupabase
:DBPrisma
:ORM
実務ではプロジェクトの規模や要件、今後の展望によって変わる部分もあると思いますが、一例として参考になれば幸いです。
実装を含めた具体例はまた別の記事にまとめていきたいと思います。