🫡

オレオレフロントエンドアーキテクチャ:Next.js app directoryが定着する前に

2023/05/01に公開

こんにちは、都内でエンジニアをしているMotonosukeです。

Next.jsはアップデートが頻繁に行われており、App Router RoadmapにおいてもSuppertedのチェックマークが多くなってきたことから、今後が楽しみです。

そこで、app direcotryが定着する前に個人的にしっくりきている機能単位で設計するpagesディレクトリアーキテクチャを記事にしたいと思います。

結論

こちらにレポジトリを置いておきます。
https://github.com/Motonosuke/rest-features-architect-tailwind-kit
ざっくり言うと、feature-driven folder structureであり、src/featuresで機能ごとにファイルを作っていくアーキテクチャです。

Reactのディレクトリ構成のベストプラクティスを集めたBulletproof Reactで紹介されているアーキテクチャを参考にしています。

記事のターゲット

  • フロントエンドのアーキテクチャに悩んでいる人
  • フロントエンドのアーキテクチャにおいて、DDDやクリーンアーキテクチャがしっくりこなかった人
  • フロントエンドを学習し始めてアーキテクチャの右も左も分からない人

基本技術

  • TypeScript
  • Next.js
  • Recoil
  • SWR
  • Zod
  • Tailwind CSS

ディレクトリ構成

src
├── components        # アプリケーション全体で使用できる共通コンポーネント
├── constants         # pathや環境変数などをエクスポートするところ
├── features          # 機能ベースモジュール
├── hooks             # アプリケーション全体で使用できる共通hooks
├── libs              # ライブラリをアプリケーション用に設定して再度エクスポートしたもの
├── providers         # アプリケーションのすべてのプロバイダー
├── stores            # グローバルステートを一元管理するところ
├── page-components   # featuresのコンポーネントを集約するところ
├── pages             # Nextjsのルーティングシステム
├── styles            # アプリケーション全体で使用されるCSS(上書きなどを想定)
├── types             # アプリケーション全体で使用される基本的な型の定義
└── utils             # 共通のユーティリティ関数

src/components

comonentsでは、HeaderやFooterなど共通パーツや、Elementsファイル内にボタンやスピナーなど粒度の小さいUI要素を置く場所です。

components
├─ Elements/
│  ├─ Button/
│  │  ├─ Button.tsx/
│  │  ├─ Button.stories.tsx/
│  │  ├─ index.ts/
├─ Header/
├─ Footer/
├─ Layouts/

src/components/Layouts

LayoutsではMainLayoutやErrorLayoutなどを置きます。
_app.tsxでproviderを多くネストさせないために、_app.tsxでは必要最低限の処理を書き、他はLayout毎にに定義してFirst Load JS shared by allを少なくさせることでパフォーマンスの最適化を行っています。

─ Layouts/
  ├─ MainLayout.tsx/
  ├─ ErrorLayout.tsx/
  ├─ index.ts/
// MainLayout
  <RecoilRoot>
     <GlobalStateMainLayoutProvider>
       <MainHeader />
       <main>
         <div>{page}</div>
       </main>
     </GlobalStateMainLayoutProvider>
   </RecoilRoot>
// ErrorLayout
    <>
      <MainHeader />
      <main>
        <div>{page}</div>
      </main>
    </>

src/features

components、hooksなどこのディレクトリ階層は必要に応じて増えたり、減ったり(validates、tests、storiesなど)します。

features
├─ posts/
│  ├─ components/
│  │  ├─ Posts.tsx/
│  │  ├─ index.ts/
│  ├─ hooks/
│  │  ├─ useHoge.ts/
│  │  ├─ index.ts/
│  ├─ apis/
│  │  ├─ get-posts.ts/
│  │  ├─ index.ts/
│  ├─ types/
│  │  ├─ posts-type.ts/
│  │  ├─ index.ts/
│  ├─ validates/
│  ├─ tests/
│  ├─ stories/

src/page-components

featuresのcomponentをレイアウトに合うように置いていく場所です。
基本的にdivタグとcoponentでスタイルを調整して、表示するページ要件に合うように配置していきます。
Nextjsのpagesルーティング内でなるべく依存関係を作らないように一つレイヤーを挟むイメージです。

page-components
├─ posts/
│  ├─ Page.tsx/
│  ├─ index.ts/ 
// Page
 <div>
    <div>
      <Posts />
    </div>
    <div style={{ margin-top: '10px' }}>
      <Albums />
    </div>
 </div>

命名規則

コード命名規則はESlintの設定に準ずる。

src/のディレクトリ・ファイル命名規則

対象ファイル 命名規則
components パスカルケース(PascalCase)
page-components パスカルケース(PascalCase)
hooks キャメルケース(camelCase)
上記以外 ケバブケース(kebab-case)

以下を参考
vercel/commerce

src/pagesのpath(url)について
以下参考に2単語以上の英単語の場合はケバブケース(kebab-cse)を推奨。
Google検索セントラル

気をつけたいこと

このディレクトリ構成で気をつけないと破綻してしまうであろうポイントを紹介します。
それは機能の粒度をエンジニアだけで考えないということです。

機能の粒度や分割点をエンジニアだけで考えてしまうと、このディレクトリ構成をただ踏襲しても破綻してしまうと思います。冒頭で紹介したBulletproof Reactをそのまま採用しても同じです。機能を考えているデザイナーやPMと一緒に考えるのがいいでしょう。

こちらはsakitoさんの記事にしている引用なのですが、私もfeatureをどの粒度で分けてディレクトリを切っていくかがとても大切だと考えています。粒度のルールがない状態でチームで話し合いをせずに各々の判断でディレクトリを切っていくと規模が大きくなってきた時に管理が難しくなってくると思います。

さいごに

いかがだったでしょうか?
記事内で触れたように、アーキテクチャの選定には必ずしも正解がないという点を理解することが重要です。
チームメンバーのスキルや人数、サービスの規模など、多くの要素を考慮し、様々な設計手法を学ぶことで、現在のチームに適した選択肢を見つけられると思います。そうすれば、サービスが成長した際に保守が容易で生産性が向上すると考えられます。

最後までお読みいただきありがとうございました!!

その他

参考記事・ディレクトリ
https://github.com/alan2207/bulletproof-react
https://zenn.dev/sakito/articles/af87061a5016e6
https://zenn.dev/takepepe/articles/nextjs-getlayout-chunk-relation

参考にさせていただきました🙇

おまけ

uhyoさんが作っているこちらのライブラリを入れてより強いルールで制限したいと考えています(追加したら更新します)。
https://github.com/uhyo/eslint-plugin-import-access

Discussion