はじめに
最近はもっぱらWebの開発を進めております。
Next.jsとTypeScriptを使用しているのですが、Next.jsのバージョン13からAppRouterという概念が追加されました。
これが中々に大規模な変化だったようで、私自身も触っていて疑問に思うことも多々あったので今回はこのAppRouterという機能について、まとめてみたいと思います。
そもそもルーティングとは何なのか?
Webを触っていらっしゃる方であればルーティングについては既にご存知だと思いますが、そもそもルーティングとは何かというところからまとめておきたいと思います。
ルーティングとは一言で言うと、
リクエストに対して適切な処理(ページやAPIなど)を紐付ける仕組み
のことです。
ルーティングは、WebアプリケーションやAPIで重要な役割を果たす仕組みで、ユーザーが特定のURL(例えば /about や /products/123)にアクセスした際に、どのリソースや機能を提供するかを決定します。
AppRouterとは何か?
前項でルーティングとは何かという点を簡単にまとめてみましたが、次は本題であるAppRouterに触れていきます。
そもそもAppRouterとは何かという点ですが、一言で言うと、
Next.js 13以降で提供される、柔軟で効率的な新しいルーティングシステム
になります。
これまではPagesRouterという方法が主流でしたが、今後はAppRouterを軸として構築していくことになります。
従来のPagesRouterとAppRouterの主な違い
新しく登場したAppRouterですが、これまでのルーティングシステムであったPagesRouterと何が違うのでしょうか?
そこで、違いを表にしてみたいと思います。
特徴 | PagesRouter(pages/ ) |
AppRouter(app/ ) |
---|---|---|
ルーティングの仕組み | ファイル名とディレクトリ構造に基づいたルーティング。 | 同様にファイルシステムに基づくが、階層的で柔軟性が高い。 |
レンダリング方式 | クライアントサイドとサーバーサイドを分けて実装(SSR/CSR)。 | デフォルトでReact Server Components(RSC)をサポート。 |
レイアウトの共有 | 各ページで手動で共有レイアウトを指定(getLayoutなどを使用)。 |
layout.js を使用して階層的にレイアウトを簡単に共有可能。 |
デフォルトのレンダリング | クライアントコンポーネント(CSR)が中心。 | サーバーコンポーネント(RSC)がデフォルト。 |
ローディングとエラー処理 | ローディングやエラーページを個別にカスタム実装。 |
loading.js やerror.js で標準化された処理が可能。 |
APIルート |
/api ディレクトリ内に定義。 |
app/api ディレクトリ内でルーティングと統合的に定義可能。 |
Streaming対応 | 非対応。 | 対応。サーバーから部分的にHTMLをストリーム配信可能。 |
Reactの新機能利用 | SuspenseやStreamingは手動実装が必要。 | React 18以降の新機能(Suspense、Streamingなど)に最適化済み。 |
構造の柔軟性 | ページ単位で独立した構造。 | ページ、レイアウト、ローディング、エラーなどの役割が分離。 |
パフォーマンス | 初期レンダリングはSSRまたはCSRに依存。 | サーバーコンポーネントを活用した高速レンダリングが可能。 |
導入時期 | Next.jsの初期から存在。 | Next.js 13以降で正式導入。 |
推奨用途 | 従来からのプロジェクトやシンプルな構造のアプリに適している。 | 新規プロジェクトや複雑なUIが必要なアプリに最適。 |
PagesRouterはシンプルで従来の手法に適しており、SSR/CSRを手動で制御するのが一般的ですが、AppRouterは最新のReact機能に対応し、柔軟性やパフォーマンスを重視した設計となっています。
AppRouterでページを作る方法
AppRouterではフォルダがページとなる為、以下のようなフォルダ構成で実現が可能です。
app/
├── page.tsx
└── about/
└── page.tsx
AppRouterではページの数だけpageファイルが必要になります。
AppRouterでの特殊なファイル一覧
App Routerでは、特定の名前を持つファイルが特別な役割を果たします。
以下にその特殊なファイルを一覧として詳細をまとめてみます。
ファイル名 | 役割 | 備考 |
---|---|---|
page.tsx |
ページコンポーネントを定義。URLに直接対応するページを表す。 | 必須ではないが、URLごとに定義するのが一般的。エントリーポイントとしてReact.FC 型などを使用するのが推奨。 |
layout.tsx |
レイアウトコンポーネントを定義。指定された階層の子ページや子レイアウトで共有される。 | 再利用可能なヘッダー、フッター、サイドバーなどを定義するのに便利。React.FC 型を使用可能。 |
loading.tsx |
ページやデータがロード中の状態を表示するコンポーネント。 | Suspenseを使用して非同期のデータやコンポーネントが読み込まれる間に表示される。型定義不要で単純なJSX。 |
error.tsx |
ページや子コンポーネントでエラーが発生した場合に表示されるコンポーネント。 | エラー情報や再試行ボタンなどを含む。ErrorBoundary の型を活用可能。 |
not-found.tsx |
該当するページが見つからなかった場合に表示される404エラーページを定義。 | ページ階層ごとにカスタム404エラーページを作成可能。 |
head.tsx |
ページの<head> 要素内に挿入するメタデータ(タイトル、metaタグなど)を定義。 |
export default で型を指定可能。例: HeadProps またはReact組み込みの型。 |
template.tsx |
レイアウトに似ているが、特定のページ間で状態を維持しないレイアウトを定義する。 | 一時的なビューや異なる状態を扱う場合に便利。React.FC 型を使用可能。 |
route.ts |
APIエンドポイントを定義する。 | 型付きのリクエストとレスポンスを利用可能。NextRequest やNextResponse 型を使用する。 |
ディレクトリ構造の例
以下は、上記一覧のファイルを使用する場合においてapp/
ディレクトリの構造例です
app/
├── layout.tsx // 全体の共通レイアウト
├── page.tsx // ルートページ (/)
├── about/
│ ├── layout.tsx // Aboutセクション全体に共通するレイアウト
│ ├── page.tsx // Aboutページ (/about)
│ ├── loading.tsx // Aboutページがロード中の状態
│ ├── error.tsx // Aboutページでのエラー処理
├── blog/
│ ├── [slug]/
│ │ ├── page.tsx // 個別のブログ記事 (/blog/:slug)
│ │ ├── not-found.tsx // 該当するブログ記事が見つからない場合のページ
├── api/
│ ├── hello/
│ │ └── route.ts // APIエンドポイント (/api/hello)
AppRouterでの特殊なディレクトリ構造一覧
AppRouterでは、ファイルと同様に特殊なディレクトリ構造が存在します。
複数ある為、ファイルと同様に以下一覧としてまとめてみます。
フォルダ名/ファイル名 | 役割 | 詳細 |
---|---|---|
[param] |
動的ルート | URL内の特定の動的部分を定義する。例: /blog/:slug
|
[[param]] |
オプショナル動的ルート | パラメータが省略可能な動的ルートを定義する。例: /docs または /docs/:section
|
(group) |
ルートグループ | URL構造に影響を与えず、ルートを整理するためのフォルダ。例: /home はそのまま(marketing)/home/ で管理可能。 |
api/ |
APIルート | サーバーサイドAPIエンドポイントを定義するためのフォルダ。例: /api/users
|
@folder |
カスタムモジュールエイリアス | 特定のフォルダをモジュールエイリアスとして使用可能。ビルド設定で解決される。 |
.folder |
非公開または無視されるフォルダ | 通常のファイル探索やルーティングの対象外になる(ただし、設定次第で利用可能)。 |
_file |
特定用途向けのファイル(設定用) | 通常のエクスポートでは使用されず、内部的な目的や設定向けに利用される。 |
ディレクトリ構造例
app/
├── (marketing)/ // URLに影響しないルートグループ
│ ├── home/
│ │ ├── page.tsx // URL: /home
├── blog/
│ ├── [slug]/
│ │ ├── page.tsx // URL: /blog/:slug
├── docs/
│ ├── [[section]]/
│ │ ├── page.tsx // URL: /docs または /docs/:section
├── @header/ // グローバルなレイアウトやコンポーネント
│ ├── page.tsx
├── (.)internal/ // プライベートなルートグループ
│ ├── settings/
│ │ ├── page.tsx // URL: /settings
├── _shared/ // 静的アセットや共通のライブラリ
│ ├── components.tsx
├── api/
│ ├── users/
│ │ └── route.ts // URL: /api/users
各フォルダの詳細と使用例
[param]
(動的ルート)
- 役割: URLに動的な部分を定義します。
- 例:
app/blog/[slug]/page.tsx
この場合、[slug]はブログ記事のスラッグを受け取り、/blog/my-articleのようなURLに対応します。
-
利用シーン:
- 商品詳細ページ(/products/:id)
- ブログ記事(/blog/:slug)
[[param]]
(動的ルート)
- 役割: パラメータが省略可能な動的ルートを定義する。
- 例:
app/docs/[[section]]/page.tsx
この場合、[[section]] はオプションで、/docs または /docs/getting-started のように動作します。
-
利用シーン:
- ドキュメントページ(オプションのセクション)
- フィルタリング機能
(group)
(ルートグループ)
- 役割: URL構造には影響を与えず、フォルダ内でルートを整理する為に使用する。
- 例:
app/(marketing)/home/page.tsx
app/(marketing)/about/page.tsx
この場合、URLは /home や /about のままですが、マーケティング関連のページをまとめて管理できます。
-
利用シーン:
- 特定のセクション(例: マーケティング、管理者向け機能)の整理
- レイアウト共有の簡略化
@folder
(共有コンポーネント用フォルダ)
- 役割: グローバルな共有コンポーネントやユーティリティを格納する。
- 例:
app/@header/page.tsx
@header
フォルダに定義したレイアウトやコンポーネントが、特定のセクションで使われる。
-
利用シーン:
- レイアウトの動的な変更。
(.)folder
(プライベートルートグループ)
- 役割: ルートグループのように使用されるが、内部的にのみ使用されるパスとして機能し、URLに影響を与えない。
- 例:
app/(.)private/home/page.tsx → URL: /home
-
利用シーン:
- 内部的な整理や一時的なグループ化。
_folder
(静的アセットやグローバル設定)
- 役割: 静的アセットやグローバル設定、通常はページとして扱わないファイルを格納。
- 例:
app/_shared/components.tsx
-
利用シーン:
- コンポーネントライブラリやスタイルガイドを格納。
api/
(APIルート)
- 役割: サーバーサイドAPIのエンドポイントを定義する為のフォルダ。
- 例:
app/api/users/route.ts
この場合、 /api/users エンドポイントが作成され、GETやPOSTリクエストを処理できます。
-
利用シーン:
- ユーザーデータの取得(GET /api/users)
- フォーム送信処理(POST /api/contact)
AppRouterで追加された機能
AppRouterが導入され、追加された機能が複数あります。
その中で目立つものをいくつかピックアップしてまとめてみます。
1. React Server Components(RSC)のサポート
React Server Components(RSC) は、サーバーサイドでコンポーネントをレンダリングし、クライアントに効率的に配信する機能です。
詳細
- デフォルトでサーバーサイドレンダリングされるコンポーネントを作成可能。
- クライアント側でのみ動作が必要な場合は、
'use client'
を明示的に記載。 - サーバーで非同期データを取得し、最小限のクライアントJavaScriptを生成。
利点
- パフォーマンス向上(軽量なクライアントバンドル)。
- SEOに強い。
2. Server Actions(サーバーアクション)
Server Actionsは、App Routerで提供される新機能で、クライアントコンポーネントからサーバーサイドの関数を直接呼び出すことができる仕組みです。
これにより、APIルートを作成する手間を省き、コードを簡潔に保ちながら、安全かつ効率的にサーバーサイドロジックを実行できます。
詳細
-
サーバーサイドで動作:
サーバーサイドで直接動作する関数を定義し、クライアントから呼び出すことが可能です。 -
APIルート不要:
従来必要だったAPIルートを作成することなく、サーバーサイド処理を簡潔に実装できます。 -
型安全:
TypeScriptを使用して、関数の引数や戻り値に対して型を定義できます。 -
セキュア:
サーバー上で動作するため、データベース接続情報やAPIキーなどの機密情報を安全に扱えます。 -
キャッシュ制御:
revalidatePath
を使用して、指定されたパスのキャッシュをリバリデートできます。
利点
-
簡潔なコード:
クライアントとサーバー間のやり取りをシームレスに統合し、従来のAPIルートを作成する手間を省きます。 -
型安全:
TypeScriptで引数や戻り値を型付けすることで、コードの安全性と開発効率を向上させます。 -
パフォーマンスの向上:
サーバーサイドで直接処理を行うため、不要なネットワーク通信を削減し、アプリケーションの応答性を向上させます。 -
セキュリティ:
機密データを安全に処理できるため、信頼性の高いアプリケーションを構築できます。
3. フック
Next.js App Routerでは、サーバーサイドとクライアントサイドの連携を効率化する為に、新しいフックが追加されました。
これらのフックを活用することで、キャッシュの管理やナビゲーション、動的なデータフェッチが簡単に行えます。
詳細
-
usePathname
現在のURLのパス名を取得するフックです。-
特徴:
- URLが変更されるたびにパス名を再レンダリングなしで取得。
- ルーティングに関連したロジックに使用可能。
-
特徴:
-
useSearchParams
現在のURLのクエリパラメータを取得するフックです。-
特徴:
- クエリパラメータが変更されるたびに自動的に反映。
- クエリパラメータを簡単に取得、操作可能。
-
特徴:
-
useRouter
クライアントサイドのナビゲーションとルーティングを管理するためのフックです。-
特徴:
- プログラム的にナビゲーションを行える。
-
push
,replace
,prefetch
などのメソッドを使用可能。
-
特徴:
-
useParams
現在の動的セグメント(パラメータ)を取得するフックです。-
特徴:
- 動的ルートで定義されたパスパラメータを簡単に取得。
- 動的なページコンポーネントで使用。
-
特徴:
利点
-
簡単なナビゲーション管理
プログラム的なルーティングやナビゲーションが簡単に行える。 -
リアクティブなデータ取得
URLやクエリの変更に即座に対応するリアクティブなUIを構築可能。 -
コードの簡潔さ
各フックは特定の役割に特化しており、複雑なコードを簡素化できます。 -
型安全性
TypeScriptでフックを使用する場合、ルートパラメータやクエリパラメータの型を指定可能。
注意点
-
SSRとの組み合わせ:
クライアントサイド専用のフックであるため、SSRコンテキストでは使用できません。 -
動的ルートの設計:
動的パスパラメータを使用する際、ルートの構造に応じて適切にフックを配置する必要があります。
4. fetchのキャッシュ機能(デフォルトキャッシュ)
Next.js App Routerでは、fetch
関数を使用する際、2回目以降のリクエストでキャッシュされたデータが自動的に使用される仕組みが導入されました。
これにより、効率的なデータ取得が可能になります。
詳細
-
デフォルトのキャッシュ動作:
- App Router内で
fetch
を使用すると、リクエスト結果が自動的にキャッシュされます。 - キャッシュされたデータは、同じリクエストが発生した際に再利用されます。
- App Router内で
-
fetch
のオプション:- キャッシュの動作をカスタマイズするためのオプションが用意されています。
-
cache: 'force-cache'
:
強制的にキャッシュされたデータを使用します。 -
cache: 'no-store'
:
キャッシュを使用せず、常に新しいデータを取得します。 -
next.revalidate
:
一定時間後にキャッシュを無効化して再フェッチする設定。
-
- デフォルトでは
cache: 'force-cache'
が使用されます。
- キャッシュの動作をカスタマイズするためのオプションが用意されています。
-
キャッシュの再検証:
- サーバー側でのデータ再検証を
revalidatePath
やrevalidateTag
を用いて手動で行うことも可能です。
- サーバー側でのデータ再検証を
利点
-
パフォーマンスの向上:
- キャッシュにより、リクエスト回数が削減され、データ取得の高速化が実現されます。
-
開発の簡素化:
- キャッシュ機能がデフォルトで有効なため、開発者が特別な設定をする必要がありません。
-
柔軟なカスタマイズ:
- キャッシュ動作を細かく制御できるため、要件に応じたデータ取得ロジックを簡単に構築可能です。
-
サーバー負荷の軽減:
- 冗長なリクエストを防ぐことで、サーバーリソースを効率的に利用できます。
注意点
-
キャッシュの有効性
- キャッシュの動作はサーバーサイドで実行されるため、クライアントサイドでのキャッシュ管理とは異なります。
- キャッシュを使用する場合、データの更新タイミングに注意が必要です。
-
動的データの扱い
- リアルタイム性が求められるデータ(例: 最新のユーザー情報や通知など)では、
cache: 'no-store'
を設定して常に最新データを取得する必要があります。 - また、
next.revalidate
を利用してキャッシュを一定間隔で更新する方法もあります。
- リアルタイム性が求められるデータ(例: 最新のユーザー情報や通知など)では、
-
キャッシュのリバリデーション
-
revalidatePath
やrevalidateTag
を使用すると、特定の条件下でキャッシュを手動で更新できます。 - 例: ユーザーがフォームを送信した後にページデータをリバリデートする場合。
-
まとめ
過去にPagesRouterを触っている方からしたら、かなり学習コストがかかりそうな印象がありました。
ただ公式でもこれからはAppRouterを推奨するとのことで、間違いなく置き換わっていくものと思います。
私もWebにおいてはまだまだ知らないことも多いですが、少しずつ慣れていこうと思います。
さいごに
医者にめちゃくちゃ怒られたので本気で減量中です・・・