初心者に優しい Next.js app router マイベストフォルダ設計
概要
Next.js app routerでは、様々な記事でフォルダ設計が紹介されているものの、まだまだ課題があると感じております。
私が試してみて、これがベストなんじゃないかと思うフォルダ設計(2025年2月時点)がありますので、それを紹介したいと思います。
現状、全フロントエンドエンジニアがapp routerをキャッチアップしているわけではないと思う(少なくとも私の会社はそう)ので、「初心者に優しい」「難しすぎない」というのを意識してます。
フロントエンドとしてVueやReactは書いているが、app routerを実務であまり使ったことのない方も入りやすいようにと思って考えました。
結論
src/
├── app/
│ ├── user-list/
│ │ ├── page.tsx
│ │ └── _container/ ← ポイント
│ │ └── _actions/ ← ポイント
│ │ └── _utils/ ← ポイント
│ │ └── fetcher/
│ ├── page.tsx
│ └── layout.tsx
├── constants/
├── components/
│ ├── Button.tsx
├── features/ ← ポイント
│ ├── user-list/
│ │ └── page/
│ │ └── components/
│ │ └── hooks/
├── hooks/
├── libs/
├── stores/
├── types/
├── utils/
├── server/(バックエンドも実装するなら)
├── middleware.ts
解説
layout.tsxやmiddleware.tsなど、Next.js自体の概念の説明になるようなファイルは説明省略します。
app
ルーティング・server components・server actionsを置くところ
page.tsx
ルーティングの実装。
後に説明するcontainerを置く。
_container
該当画面で使うcontainer
presentational/containerパターンについては参考記事でご確認いただけますと。
ここでは主にデータ取得をする非同期コンポーネント(server components)を置きます。
取得した値をクライアントコンポーネントにpropsで渡すイメージ。
以前はpage.tsxで全部書いていたのですが、page.tsxが肥大化してカオスになった思い出(泣)があるので、分けてみたという感じ。
データ取得が複数発生するpageだとカオスになるので、この設計が自分の中ではしっくりきており、ここがポイント。
containerは該当ページに依存しますので、page.tsxと同じ階層にフォルダを置いております。
また、ルーティングと分けるために「_」をつけています。
_actions
該当画面で使うserver actions
データの更新などのactionsを定義します。
ページ毎に分けずに、src直下にactionsを置くスタイルをよくみるが、これだとactionsが肥大化して辛いので、ページ毎に分けました。
各所で使用するserver actionsがあるのなら、src直下のactionsに置くとよいです。
_utils
page.tsx・_container・_actionsで使う関数をここにまとめています。
基本fetch関数を書くだけです。
utilsという汎用的な名前でまとめてしまいましたが、もっといい命名があるかも。
components
複数の画面で使う共通コンポーネント
buttonとか
hooks
複数の画面で使う共通hooks
libs
サードパーティライブラリの設定やそれを使った実装
stores
グローバルステートの管理
jotaiとか使って管理するステートを書く
types
複数の画面で使うtype定義
utils
複数の画面で使うutility関数
constants
固定値を定義するファイル群
features
特定の機能・ページでのみ使用するファイルをまとめる。
package by featureってやつです。
自分は、ページ別で分けていくイメージで使ってます。
app配下のフォルダと合わせる形がわかりやすいです。
- app/user-list
- features/user-list
features配下は基本クライアントコンポーネントのファイル
非同期コンポーネントやサーバーのデータ取得処理はなく、クライアントのjavascriptで画面を作っていくところ
features/[ページ名]/page
componentsやhooksを組み合わせて、ここでページを実装する。
app/[ページパス]/_container/Containerファイルでimportされる想定。
containerで取得したデータをpropsで受け取る。
features/[ページ名]/components
該当ページでのみ使われるcomponents
features配下のファイルは、ページ毎に作るので、ページと密にして良いイメージです。
疎で抽象的なcomponentsはsrc直下のcomponentsに作ります。
features/[ページ名]/hooks
該当ページでのみ使われるhooks
その他、features/[ページ名]/componentsと同様
featuresその他
該当ページでのみ使われるtypesやutilsがあれば、それもcomponentsなどと同じようにfeatures/[ページ名]配下に置きます。
server(バックエンドも実装しちゃう場合)
このフォルダ配下にバックエンドの実装を書く
repositoryやuseCaseなどのフォルダを置いて従来のバックエンド開発に寄せるのがいいと思う。
良かったところ
- page.tsxやactionsの肥大化が防げるので、ファイル内のコードがシンプルになり、可読性向上・単体テストがしやすくなった
- serverとclientの境目がわかりやすい
- app router初心者の方も入りやすい(と思ってる)。(page.tsxに近いところでデータ取得をしているので、pages routerを書いていた人でも入りやすそう。従来のフロントエンドでやりがちな設計を踏襲している。)
微妙なところ
- propsのバケツリレーになってしまうことがある
- pages routerのgetServersidePropsとあんまり変わらん?
- featuresって機能単位のものだけど、ページ単位になっているが良いの? → わかりやすいので厳密に考えずに、ページで分けることにした
まとめ
app router経験の浅い方も入りやすいように、シンプルに考えられるような設計を意識しております。
特にポイントとしては、サーバーとクライアントの境目をわかりやすくしたところです。
featuresの中にcontainersを置くなども考えたのですが、featuresの中でクライアントとサーバーファイルどちらも共存するので、初心者の方には難しいかもと思い、app側にサーバーコンポーネントをまとめました。
また、規模が大きくなるとどんどん肥大化してしまうactionsやpage.tsxを軽くするようにフォルダ分けをしました。
それ以外はフロントエンドで採用されがちなpackage by featureを使っています。
従来のフロントエンドのエッセンスをベースとしつつ、app routerの特徴を反映させたような感じで、自分は気に入ってます。
Discussion