Next.js 14 App Router - ルーティング
ソース: https://nextjs.org/docs/14/app/building-your-application/routing
基本用語
- Tree: 階層構造を可視化する規約(コンポーネントツリー、フォルダ構造等)
- Subtree: ツリーの一部。新しいルート(最初)からリーフ(最後)まで
- Root: ツリーまたはサブツリーの最初のノード(ルートレイアウトなど)
- Leaf: 子を持たないノード(URLパスの最後のセグメント)
- URL Segment: スラッシュで区切られたURLパスの一部
- URL Path: ドメインの後に続くURL部分(セグメントで構成)
App Router の基本
appディレクトリ内で動作- デフォルトで Server Components
- React Server Components 上に構築
- 共有レイアウト、ネストされたルーティング、ローディング状態、エラーハンドリングをサポート
フォルダとファイルの役割
- フォルダ = ルートを定義
- ファイル = ルートセグメントに表示されるUIを作成
app/
├── layout.tsx # ルートレイアウト(必須)
├── page.tsx # `/` のUI
├── dashboard/
│ ├── layout.tsx # dashboardレイアウト
│ ├── page.tsx # `/dashboard` のUI
│ └── settings/
│ └── page.tsx # `/dashboard/settings` のUIルートセグメント
各フォルダ = ルートセグメント = URLセグメントに対応
特殊ファイル規約
| ファイル | 役割 |
|---|---|
layout.js | セグメントとその子の共有UI |
page.js | ルート固有のUI(ルートを公開アクセス可能にする) |
loading.js | セグメントとその子のローディングUI |
not-found.js | 404 UI |
error.js | エラーUI |
global-error.js | グローバルエラーUI |
route.js | サーバーサイドAPIエンドポイント |
template.js | 再レンダリングされるレイアウトUI |
default.js | Parallel Routesのフォールバック |
.js,.jsx,.tsxいずれも使用可能
コンポーネント階層(レンダリング順序)
<Layout>
<Template>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<ErrorBoundary fallback={<NotFound />}>
<Page />
</ErrorBoundary>
</Suspense>
</ErrorBoundary>
</Template>
</Layout>ネストされたルートでは、親セグメントのコンポーネントの内側に子セグメントのコンポーネントがネストされる。
コロケーション
app ディレクトリ内にコンポーネント、スタイル、テストなどを配置可能。 page.js または route.js の内容のみが公開アクセス可能。
ルートの定義
ソース: https://nextjs.org/docs/14/app/building-your-application/routing/defining-routes
- フォルダでルートを定義
page.jsファイルでルートセグメントを公開
// app/page.tsx → `/`
export default function Page() {
return <h1>Hello, Next.js!</h1>
}page.js がないフォルダはルートとして公開されない(コンポーネントやスタイルの配置に使える)。
Pages と Layouts
ソース: https://nextjs.org/docs/14/app/building-your-application/routing/pages-and-layouts
Pages
- ルート固有のユニークなUI
page.jsからコンポーネントをエクスポート- デフォルトで Server Component
- ルートサブツリーのリーフ
// app/dashboard/page.tsx → `/dashboard`
export default function Page() {
return <h1>Hello, Dashboard Page!</h1>
}Layouts
- 複数ページ間で共有されるUI
- ナビゲーション時に状態を保持、再レンダリングしない
childrenプロップを受け取る
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<section>
<nav></nav>
{children}
</section>
)
}重要な注意点:
- 親レイアウトと子の間でデータの受け渡しは不可(同じデータを複数箇所でfetchしてOK、Reactが自動重複排除)
- レイアウトは下位のルートセグメントにアクセスできない(
useSelectedLayoutSegment/useSelectedLayoutSegmentsを使う)
Root Layout(必須)
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}appディレクトリに必須<html>と<body>タグを定義する必要がある- Server Component であり、Client Component にできない
ネストされたレイアウト
- フォルダ内のレイアウトはそのルートセグメントに適用
- デフォルトでネスト(
childrenプロップで子レイアウトをラップ) - Root Layout のみ
<html>と<body>を含める
Templates
- レイアウトと似ているが、ナビゲーション時に新しいインスタンスを作成
- DOMが再作成、状態は保持されない、effectsが再同期
- ユースケース:
useEffectでのページビュー記録、ページごとのフィードバックフォーム
// app/template.tsx
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}レンダリング順序:
<Layout>
<Template key={routeParam}>{children}</Template>
</Layout>Metadata の設定
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}Root Layout に手動で
<title>や<meta>を追加しない。Metadata API を使用する。
Dynamic Routes
ソース: https://nextjs.org/docs/14/app/building-your-application/routing/dynamic-routes
基本
フォルダ名を角括弧で囲む: [folderName]
// app/blog/[slug]/page.tsx
export default function Page({ params }: { params: { slug: string } }) {
return <div>My Post: {params.slug}</div>
}| ルート | URL例 | params |
|---|---|---|
app/blog/[slug]/page.js | /blog/a | { slug: 'a' } |
generateStaticParams
ビルド時にルートを静的に生成:
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({ slug: post.slug }))
}Catch-all Segments
| パターン | 例 | マッチ |
|---|---|---|
[slug] | /blog/a | 単一セグメント |
[...slug] | /shop/a/b/c | 1つ以上のセグメント({ slug: ['a','b','c'] }) |
[[...slug]] | /shop も /shop/a/b も | 0個以上のセグメント |
TypeScript
// app/blog/[slug]/page.tsx → { slug: string }
// app/shop/[...slug]/page.tsx → { slug: string[] }
// app/shop/[[...slug]]/page.tsx → { slug?: string[] }
// app/[catId]/[itemId]/page.tsx → { categoryId: string, itemId: string }Route Groups
ソース: https://nextjs.org/docs/14/app/building-your-application/routing/route-groups
フォルダ名を丸括弧で囲む: (folderName) → URLパスに影響しない
用途
- ルートの整理: サイトセクション、チームごとにグループ化
- 同じセグメントレベルでネストされたレイアウト
- 複数のRoot Layoutの作成
app/
├── (marketing)/
│ ├── layout.tsx # マーケティング用レイアウト
│ ├── about/page.tsx # /about
│ └── blog/page.tsx # /blog
├── (shop)/
│ ├── layout.tsx # ショップ用レイアウト
│ ├── cart/page.tsx # /cart
│ └── account/page.tsx # /account注意:
(marketing)/about/page.jsと(shop)/about/page.jsは同じ/aboutになりエラー- 複数Root Layoutを使う場合、
home page.jsはいずれかのRoute Groupに定義 - 複数Root Layout間のナビゲーションはフルページロード
リンクとナビゲーション
ソース: https://nextjs.org/docs/14/app/building-your-application/routing/linking-and-navigating
3つのナビゲーション方法
1. <Link> コンポーネント(推奨)
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}<a>タグを拡張- プリフェッチとクライアントサイドナビゲーションを提供
- Dynamic Segmentsへのリンク: テンプレートリテラルを使用
- アクティブリンクの検出:
usePathname()を使用 - スクロール制御:
scroll={false}で無効化
2. useRouter Hook
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}- Client Components でのみ使用
- Server Components では
redirect()を使用
3. ネイティブ History API
window.history.pushState: 新しいエントリを追加(戻る操作可能)window.history.replaceState: 現在のエントリを置換(戻る操作不可)
ナビゲーションの仕組み
- コード分割: ルートセグメントごとに自動分割
- プリフェッチ:
<Link>がビューポートに表示されると自動プリフェッチ- 静的ルート: 全体をプリフェッチ・キャッシュ
- 動的ルート: 最初の
loading.jsまでの共有レイアウトをプリフェッチ(30秒キャッシュ)
- キャッシング: Router Cache(インメモリクライアントサイドキャッシュ)
- 部分レンダリング: 変更されたセグメントのみ再レンダリング
- ソフトナビゲーション: ページリロードなし、React状態を保持
- 戻る/進むナビゲーション: スクロール位置を維持、Router Cacheを再利用