App RouterとPage Routerのルーティング差分まとめ
本記事の目的
2023 年ごろ Next.js13 が発表され、従来のルーティング機能 Page Router と別に、App Router が追加されました。公式は App Router の使用を推奨していますし、発表から 1 年以上経っていて徐々に記事も増えてきたので、流石に App Router 採用に踏み切って良いと思います。
しかし、App Router はこれまでの React や Page Router による書き方と大きく異なります。これは、React Server Components(RSC) というアーキテクチャが導入され、開発の考え方が大きく変化したからです。
この記事では、Next.js 定義から Page Router と App Router の差分、ルーティングの意義について説明していきたいと思います。
Next.js とは
Next.js は、React の機能を拡張した強力なウェブ開発フレームワーク。
React は主にユーザーインターフェース(UI)の構築に特化した JavaScript ライブラリですが、Next.js は React の機能を基盤としつつ、より包括的な開発環境を提供しています。
大きな特徴として、レンダリング(ブラウザ上で UI を生成し表示する一連の処理)の方式が、SSR や SSG など、柔軟に選択できるということです。これによって、SEO 対策や、読み込み速度などのパフォーマンス向上に繋がるという利点があります。
また、Node.js と名前が似ているから、仲間かと思われるかもしれませんが、Node.js はサーバーサイドの JavaScript 実行環境であり、Next.js は Node.js 上で動作するフレームワークです。
App Router の意義
web アプリを開発する上で、異なるページや機能間のナビゲーション(移動)を設定する必要が出てきます。これをルーティングと言います。React+Vite のような構成で web アプリを作る場合は、React 自体にルーティング機能が内蔵されていないため、ルーティングを実現するために React Router などのサードパーティライブラリを使用する必要があります。これは Vue.js でも同様で、設定がやや複雑でめんどいです。
Next.js ではこのルーティングを感覚的に行うことのできる機能が備わっています。従来の Page Router と新しく追加された App Router です。どちらもディレクトリ構造でルーティングをサポートする点は同じですが、App Router は Page Router よりも複雑なルーティング設定が可能になりました。
App Router と Page Router の違い
Page Router でも使えた従来の機能は、App Router で以下のように変化しました。
Page Router
/pages
├── index.js // `/`
├── about.js // `/about`
├── blog/
│ ├── index.js // `/blog`
│ ├── [...slug].js // `/docs/*` に対応
│ └── [id].js // `/blog/:id`
├── _app.js // 全体の共通ロジック。レイアウトとか
├── _document.js // HTMLのカスタマイズ
├── api/
│ └── hello.js // `/api/hello`
App Router
/app
├── layout.js // 全体のレイアウト
├── page.js // `/`
├── about/
│ └── page.js // `/about`
├── blog/
│ ├── layout.js // `/blog` 以下の共通レイアウト
│ ├── page.js // `/blog`
│ ├── [...slug]/
│ │ └── page.js // `/docs/*`
│ └── [id]/
│ └── page.js // `/blog/:id`
├── api/
└── hello/route.js // `/api/hello`
さらに App Router にはこれに加えて、次の 4 項目が追加されました。
[[...param]]
)
1. オプショナルキャッチオール(/app
├── search/
│ ├── [[...filters]]/
│ └── page.js // `/search` または `/search/*` (検索ページ)
- 例
/search
/search/category/react
- 必須ではないパラメータに対応する際に便利
(segment)
)
2. セグメントグループ(/app
├── (auth)/
│ ├── login/
│ │ └── page.js // `/login` (ログインページ)
│ ├── register/
│ └── page.js // `/register` (新規登録ページ)
├── (marketing)/
│ ├── home/
│ │ └── page.js // `/home` (マーケティングホーム)
│ ├── pricing/
│ └── page.js // `/pricing` (価格情報ページ)
-
(auth)
や(marketing)
は URL に含まれない。グループ化したいけど、URL には干渉したくないなという時に便利
@parallel
)
3. パラレルルーティング(/app
├── dashboard/
│ ├── layout.js
│ ├── @analytics/
│ │ └── page.js // `/dashboard` の一部に含まれる
│ ├── @settings/
│ └── page.js // `/dashboard` の一部に含まれる
-
layout.js
内で受け取る特定のキー(例:@analytics
や@settings
)によって、それぞれのルートがレンダリングされる -
layout.js
が以下のようなコードの場合、import { useSearchParams } from "next/navigation"; export default function DashboardLayout({ analytics, settings, children }) { const searchParams = useSearchParams(); const view = searchParams.get("view"); return ( <div> <nav> <ul> <li><a href="/dashboard">Overview</a></li> <li><a href="/dashboard?view=analytics">Analytics</a></li> <li><a href="/dashboard?view=settings">Settings</a></li> </ul> </nav> <main> {view === "analytics" && analytics} {view === "settings" && settings} {!view && children} </main> </div> ); }
-
/dashboard
:layout.js
とpage.js
がレンダリングされる(デフォルトビュー) -
/dashboard?view=analytics
:layout.js
内で@analytics/page.js
がレンダリングされる -
/dashboard?view=settings
:layout.js
内で@settings/page.js
がレンダリングされる
-
-
同じエンドポイントで複数のビューを実現したいときに便利
4. 特殊ファイル
/app
├── error.js // エラーページ (全体で共有)
├── loading.js // ローディング表示 (全体で共有)
├── not-found.js // 404エラーページ (全体で共有)
├── dashboard/
│ ├── layout.js // `/dashboard` (共通レイアウト)
│ ├── page.js // `/dashboard`
│ ├── error.js // `/dashboard`専用のエラーページ
│ ├── loading.js // `/dashboard`専用のローディング表示
│ ├── analytics/
│ │ └── page.js // `/dashboard/analytics` (アナリティクスページ)
まとめ
以上をまとめると、App Router でできるルーティングの全容は以下の通り。
/app
├── page.js // `/` (ホームページ)
├── about/
│ └── page.js // `/about`
├── blog/
│ ├── page.js // `/blog`
│ ├── [id]/
│ │ └── page.js // `/blog/:id`
│ └── [id]/
│ └── edit/
│ └── page.js // `/blog/:id/edit`
├── dashboard/
│ ├── layout.js // `/dashboard` (共通レイアウト)
│ ├── page.js // `/dashboard`
│ ├── error.js // `/dashboard`専用のエラーページ
│ ├── loading.js // `/dashboard`専用のローディング表示
│
├── docs/
│ ├── page.js // `/docs`
│ ├── [...slug]/
│ └── page.js // `/docs/*`
├── search/
│ ├── [[...filters]]/
│ └── page.js // `/search` または `/search/*`
├── (auth)/
│ ├── login/
│ │ └── page.js // `/login`
│ ├── register/
│ └── page.js // `/register`
├── (marketing)/
│ ├── home/
│ │ └── page.js // `/home`
│ ├── pricing/
│ └── page.js // `/pricing`
├── error.js // エラーページ (全体で共有)
├── loading.js // ローディング表示 (全体で共有)
├── not-found.js // 404エラーページ (全体で共有)
├── dashboard/
│ ├── layout.js // `/dashboard` (共通レイアウト)
│ ├── error.js // `/dashboard`専用のエラーページ
│ ├── loading.js // `/dashboard`専用のローディング表示
│ ├── page.js // `/dashboard`
│ ├── @analytics/
│ │ └── page.js // `/dashboard`のアナリティクスビュー
│ ├── @settings/
│ └── page.js // `/dashboard`の設定ビュー
これにより、
- 開発者目線 → どのファイルがどこにあるのか直感的でわかりやすい!
- 利用者目線 →url が直感的でわかりやすい!
と双方良しになるという寸法です。
以上です。
Discussion