この記事では、App Routerのディレクトリ設計について、実際のプロジェクト構成を交えて解説します。
📝 この記事で使う用語
- CSR(Client Side Rendering): ブラウザ側でJavaScriptを実行してHTMLを生成する方式
- SSR(Server Side Rendering): サーバー側でHTMLを生成してからブラウザに送る方式。表示が高速になる
- Streaming: HTMLを分割して順次送信する方式。ページ全体の読み込みを待たずに表示が始まる
📖 App Routerの基本
Next.js 13以降で導入されたApp Routerでは、app/ディレクトリの構造がそのままURLになります。
page.tsxを配置したディレクトリがページとして認識され、[id]のような動的セグメントも使えます。ディレクトリ構造を見ればURLが分かるため、直感的に開発できます。
🎯 ディレクトリ設計で意識したこと
App Routerを採用したプロジェクトでは、以下の点を意識して設計しました。
- 責務の分離: app/、client/、server/など役割ごとに分離
- レイアウトの共有: 認証ページ、メインアプリなど画面種別ごとに整理
- SSRの維持: レイアウトはServer Componentに保つ
📁 全体のディレクトリ構成
役割ごとの分離
App Routerでは、app/ディレクトリにすべてのコードを置くこともできます。しかし、規模が大きくなると管理が難しくなります。
そこで、役割ごとにディレクトリを分けました。この構成は、以下の記事を参考にしています。
特に、client/とserver/を明確に分ける構成は効果的でした。Next.jsではサーバー専用モジュールをクライアントから誤って呼び出すと実行時エラーになりますが、ディレクトリレベルで分離することで、そのようなミスを防ぎやすくなります。
- app/: ルーティング定義のみ。ビジネスロジックは書かない
- client/:
"use client"が必要なコンポーネントとフック - server/: サーバーサイド専用のコード
- database/: DBスキーマ定義(Drizzle ORM)
- shared/: 両方で使える純粋な関数と型定義
- i18n/, messages/: 国際化対応
この分離により、「このコードはどこにあるか」が明確になります。
DBスキーマに合わせたディレクトリ構造
database/ディレクトリは、PostgreSQLのスキーマ構造に合わせています。
各ディレクトリがPostgreSQLのスキーマに対応しています。テーブルを探すとき、「どのスキーマに属するか」を考えれば、ファイルの場所が分かります。
server/repositories/もこのスキーマ構造に合わせているため、DBスキーマ→リポジトリ→ユースケースという流れが追いやすくなっています。
🗂️ Route Groupsの活用
Route Groupsは、URLに影響を与えずにディレクトリを整理できる機能です。
Route Groupごとにlayout.tsxを配置することで、異なるレイアウトを適用できます。認証ページはシンプルなレイアウト、メインアプリはサイドバー付きのレイアウトといった使い分けが可能です。
URLは /login、/contents のようにシンプルなまま、レイアウトだけを分離できます。
🌐 APIルーティングの設計
APIエンドポイントは、役割ごとに分離しています。
HonoをNext.jsに統合
メインのAPIはHonoで実装しています。API本体はserver/api/に配置し、app/api/にはNext.jsと接続するための最小限のコードだけを置いています。
Honoを使うメリット:
- ディレクトリ構造が自由: Next.jsのRoute Handlerは
app/api/配下にファイルを配置する必要がありますが、Honoならserver/api/で自由に整理できます - OpenAPI仕様の自動生成:
@hono/zod-openapiを使えば、APIドキュメント(openapi.json)を自動生成できます - フレームワーク非依存: 将来的にNext.js以外に移行する場合も、API部分はそのまま使えます
認証APIの分離
Better Authは専用のエンドポイントで処理します。
/api/auth/* は Better Auth、それ以外は Hono が処理する構成です。
🖥️ Server Componentsを活かす設計
App Routerの最大の利点は、Server Componentsです。この恩恵を最大化するため、レイアウトをServer Componentに保つ工夫をしています。
Before: レイアウトがClient Component
After: レイアウトはServer Componentに保つ
layout.tsx自体はServer Componentのままにして、状態管理が必要な部分だけをClient Componentとして切り出します。
こうすることで、layout.tsx配下の子ページはSSRとStreamingの恩恵を受けられます。
🔀 補足: Parallel RoutesとIntercepting Routes
より高度なルーティング機能として、Parallel RoutesとIntercepting Routesがあります。私が開発しているMemoreruでは、テーブルコンテンツの行編集機能でこれらを活用しています。
構成
Parallel Routesの仕組み
layout.tsxで複数のスロットを受け取ります。
Intercepting Routesの効果
(.)rows/[rowId]/ は、テーブル詳細ページ内でのリンククリックを検知して、別の表示方法に切り替えます。
- 通常アクセス
/contents/table/123/rows/456→ 行編集の専用ページ - テーブルから遷移 → スライドインパネルで表示
ユーザーは同じURLでも、アクセス方法によって異なるUIを体験できます。
✅ まとめ
App Routerのディレクトリ設計で意識したポイントをまとめます。
構成のポイント:
- app/はルーティング定義のみ、ロジックは書かない
- client/、server/、shared/で責務を分離
- Route Groupsでレイアウトを分離
- レイアウトはServer Componentに保つ
ディレクトリ設計に正解はありませんが、一貫したルールを決めておくと、コードの場所が予測しやすくなります。