🙌

Next.js App Routerの場合componentはどこに置けばいいの?

2023/11/06に公開

きっかけ

私は今まで Pages Router でプロジェクトの開発をしてきました。今の所、具体的な新規で開発する案件の話はないけど今後 App Router で開発するときにどうゆうフォルダ構成にすれば良いか悩んだからまとめようかなと思ったのがきっかけです。

環境について

node: v18.18.2
npm: 9.8.1
react: 18 系
next.js: 13.5.6

具体的にどんなことに悩んだのか

  • /app の中で style や util 系関数や component なんかも入れた方がいいの?
  • それとも/app を/pages と同じように扱う方がいいの?

この 2 点です。Private Folders やら Route Groups なんかもあったりして Next.js のファイルベースルーティングはいろんなことが柔軟にできるようになった印象です。
しかし、柔軟だからこそ「いや、まじでどうするのが正解なん?」ってなっているのも事実。

実際、Next.js 公式もプロジェクトファイルの整理方法や配置方法については自由にやってよってスタンスです。
もちろん例は記載してくれていますが。

https://nextjs.org/docs/app/building-your-application/routing/colocation

フォルダ構成の前提

色々試したりしたんですが、フォルダ構成は個人的に良さそうなものを考えてみました。ちなみに component の配置やフォルダ構成の基本はこれをベースにしています。component に限らず styles や types などの配置も一緒に考えます。

https://zenn.dev/brachio_takumi/articles/2ab9ef9fbe4159

├── common
│   ├── constants
│   ├── hooks
│   ├── styles
│   ├── types
│   └── utils
├── components
│   ├── functional
│   ├── layouts
│   ├── ui-elements
│   └── ui-parts
└── features

個人的に考えたフォルダ構成 2 パターン

パターンとしては下記の 2 パターンかなと思います。

  1. /srcの中の /app と同階層に component などを配置する
  2. /src を作らずプロジェクト直下に/app を作りその中に構成する

/srcの中の /app と同階層に component などを配置する

おそらくこれが一番シンプルな構成です。というのも next のプロジェクトを作成するときに src を作るかどうか聞かれます。基本的にはそのままエンターキーで src ディレクトリが作られる仕様になっています。

$ npx create-next-app@latest --ts
✔ What is your project named? … app-router-src
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No

image

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

また alias の設定もこの時点でできています。tsconfig.json を見るとsrc配下を見るようになっていますね。もちろん /src/app の中に component や style などを配置していってもいいんですけど、それだとなんか気持ち悪いというか /src ある意味なくない?って個人的にはなってしまうのでしないかなーと。

ただ、tailwind の設定を変える必要があります。今回の場合だと /features を足す必要があります。

tailwind.config.json
import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+   "./src/features/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
};
export default config;

以前までの Pages Router とほぼ同じような構成にすることができるので、慣れている部分もあるしシンプルでわかりやすい構成かなと思います。

├──src
    ├── app
    ├── common
    │   ├── constants
    │   ├── hooks
    │   ├── styles
    │   ├── types
    │   └── utils
    ├── components
    │   ├── functional
    │   ├── layouts
    │   ├── ui-elements
    │   └── ui-parts
    └── features

/src を作らずプロジェクト直下に/app を作りその中に構成する

こちらはプロジェクトの直下に /app を配置します。なので next プロジェクト作成時に src を使わないようにしないといけません。またこのパターンでは Route Groups と Private Folders を使うことでより見やすくわかりやすいフォルダ構成にすることができます。今度は src を作らないように Would you like to use src/ directory?No を選びます。

$ npx create-next-app@latest --ts
✔ What is your project named? … app-router-app
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No

image

そうすると alias もプロジェクト直下で設定されていますね。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

/app に component やルーティングフォルダーを配置すると下記のようになります。このパターンでは Route Groups と Private Folders を使った方がわかりやすくなります。

├──app
    ├── _common
    │   ├── constants
    │   ├── hooks
    │   ├── styles
    │   ├── types
    │   └── utils
    ├── _components
    │   ├── functional
    │   ├── layouts
    │   ├── ui-elements
    │   └── ui-parts
    ├── _features
    └── (routes)

Route Groups

フォルダに () をつけることでルーティングとは関係ないグループとしてまとめることができます。イメージしやすいのだと publicprivateでディレクトリを分けてログインしなくても見れるページ、ログイン後に見れるページというように分けることもできますね。

├── (public)
│   ├── login
│   ├── works
│   └── users
├── (private)
│   └── setting

URL はというと /login, /works, /users, /setting というように () のディレクトリはルーティングの URL には関係がなくできます。

Private Folders とは

次に Private Folders についてです。これは/_hogeなどのように _ をつけることでルーティングに関係のないディレクトリとすることができる機能です。
そもそも App Router のファイルベースルーティングでは ディレクトリに page.tsx ある場合に限りそのディレクトリがルーティングに使われるという特徴があります。

例えば下記みたいな感じでもルーティングとしては問題はありません。

├──app
    ├── accounts
    │   └── page.tsx
    ├── common
    │   ├── constants
    │   ├── hooks
    │   ├── styles
    │   ├── types
    │   └── utils
    ├── components
    │   ├── functional
    │   ├── layouts
    │   ├── ui-elements
    │   └── ui-parts
    ├── features
    ├── users
    │   └── page.tsx
    └── login
        └── page.tsx

ではなぜ Private Folders にしたかというと理由は 2 点あって

  1. 明示的であること
  2. ルーティングとそれ以外のフォルダの順番を担保できる

明示的であること

これは _ がついているのはルーティングじゃないんだなってことがわかりやすいですよね。

ルーティングとそれ以外のフォルダの順番を担保できる

上記の例の場合だと /accounts だけ 上の方にあって他のルーティングに関係するフォルダが下の方になっちゃいます。個人的にこうゆうのめっちゃ嫌いなんですよね。関係するフォルダはまとめるとか近くに配置させたい!

なので例に挙げたフォルダ構成であれば component などのルーティングに関係ないものと ルーティングに関係のある (routes)を明示的に分けてかつまとめることだできますね。ただ個人的には開発中にそのフォルダ内にあることを意識する必要のない /public/node_modules も alias の設定に含まれるのはちょっと気持ち悪いかなとは思いました。ただまー意識するところではないので開発していて不自由に感じることはないと思います。

├──app
    ├── _common
    │   ├── constants
    │   ├── hooks
    │   ├── styles
    │   ├── types
    │   └── utils
    ├── _components
    │   ├── functional
    │   ├── layouts
    │   ├── ui-elements
    │   └── ui-parts
    ├── _features
    └── (routes)

まとめ

いかがでしょう?個人的には新規のプロジェクトで Next.js を使うならおそらくシンプルなパターン 1 を選ぶと思います笑
ほぼほぼ default の設定のままでできちゃいますしね。

Discussion