Next.js App Routerとは?【2026年版】ディレクトリ構成・Pages Router移行・Server Components完全ガイド
コセケン
テクラル合同会社

Next.js App Routerとは、Next.js 13(2022年10月)から導入され、Next.js 15・16で完全に本番対応した新しいルーティング・レンダリングシステムです。すべてのコンポーネントがデフォルトでサーバー上で動作するReact Server Components(RSC)を基盤とし、従来のPages Routerと比べてバンドルサイズの大幅削減・初期表示の高速化・柔軟なレイアウト管理を実現します。
本記事では以下を解説します。
- App Routerの仕組みとPages Routerとの違い
- 2026年現在の推奨ディレクトリ構成(コード例付き)
- Server Components / Client Componentsの使い分け判断基準
- データフェッチとキャッシュ戦略の実践ノウハウ
- Pages Routerからの段階的移行手順
- Next.js 16の新機能(PPR安定化・Turbopack標準化)
Next.js App Routerとは何か?仕組みを3分で理解する

Next.js App Routerとは、React 18から導入されたReact Server Components(RSC)を前提に設計されたルーティングシステムです。app/ ディレクトリ以下のフォルダ構造がそのままURLパスになります。
従来のPages Routerでは、コンポーネントのロジックやライブラリがすべてブラウザに送信されていました。App Routerではデフォルトで全コンポーネントがサーバー上で実行され、生成済みのHTMLだけをクライアントに返します。これによりクライアントに送信するJavaScriptバンドルを大幅に削減できます。
App Routerの特別なファイル
App Routerでは、フォルダ内に配置するファイル名によって役割が決まります。
| ファイル名 | 役割 |
|---|---|
page.tsx |
そのURLで表示するUI(このファイルがないとルートとして公開されない) |
layout.tsx |
ネスト可能な共通レイアウト(再レンダリングされない) |
loading.tsx |
Suspenseベースのローディング UI |
error.tsx |
エラーバウンダリ(クライアントコンポーネント必須) |
not-found.tsx |
404ページ |
route.ts |
APIエンドポイント(REST API相当) |
Pages Routerとの根本的な違い
| 比較項目 | Pages Router | App Router |
|---|---|---|
| デフォルトのレンダリング | クライアントコンポーネント中心 | サーバーコンポーネント(RSC)中心 |
| データフェッチ | getServerSideProps / getStaticProps |
コンポーネント内 async/await + fetch |
| レイアウト管理 | _app.js で一律適用 |
ディレクトリごとにネスト可能な layout.tsx |
| キャッシュ制御 | ページ単位のISR | リクエスト単位のきめ細やかな制御 |
| ストリーミング | 非対応 | loading.tsx + Suspense でネイティブ対応 |
| Server Actions | 非対応 | フォームやミューテーションをサーバー関数で処理 |
新規プロジェクトではApp Routerの採用が公式推奨です。既存プロジェクトは pages/ と app/ を共存させながら段階移行できます。
Next.js App Routerのディレクトリ構成【2026年ベストプラクティス】

App Routerのディレクトリ設計で押さえるべき原則は2つです。ルーティングと共有コードを分離すること、そして機能単位でコロケーション(同じ場所に配置)することです。
推奨ディレクトリ構成(実践版)
src/
├── app/ # ルーティング専用(URLパス)
│ ├── layout.tsx # グローバルレイアウト(HTML/body タグ)
│ ├── page.tsx # トップページ(/)
│ ├── loading.tsx # グローバルローディング UI
│ ├── not-found.tsx # グローバル 404
│ ├── dashboard/
│ │ ├── layout.tsx # ダッシュボード共通レイアウト
│ │ ├── page.tsx # /dashboard
│ │ └── settings/
│ │ └── page.tsx # /dashboard/settings
│ ├── blog/
│ │ ├── page.tsx # /blog(記事一覧)
│ │ └── [slug]/
│ │ ├── page.tsx # /blog/[slug](記事詳細)
│ │ ├── loading.tsx # 記事ローディング
│ │ └── error.tsx # 記事エラー
│ └── api/
│ └── posts/
│ └── route.ts # /api/posts
├── components/ # プロジェクト全体で共有するUIコンポーネント
│ ├── ui/ # ボタン・インプット等の汎用UI
│ └── layout/ # ヘッダー・フッター等
├── lib/ # ユーティリティ・API クライアント・DB アクセス
├── hooks/ # カスタムフック(Client Components専用)
└── types/ # TypeScript型定義
コロケーション(Colocation)の活用
特定のルートでしか使わないコンポーネントは、そのルートのディレクトリ内に閉じ込めます。page.tsx 以外のファイルはURLとして公開されないため、安全に同居させられます。
app/
└── dashboard/
├── page.tsx
├── _components/ # アンダースコアでURL対象外を明示
│ ├── stats-card.tsx
│ └── recent-activity.tsx
└── _utils/
└── format-dashboard.ts
_components/ のようにアンダースコアプレフィックスを使うと、フォルダをルーティング対象から明示的に除外できます。
Route Groups でレイアウトを整理する
括弧でフォルダ名を囲むと、URLパスに影響を与えずにグループ化できます。
app/
├── (marketing)/ # URLには含まれない
│ ├── layout.tsx # マーケティング用レイアウト
│ ├── page.tsx # /
│ └── about/
│ └── page.tsx # /about
└── (dashboard)/ # URLには含まれない
├── layout.tsx # ダッシュボード用レイアウト
└── dashboard/
└── page.tsx # /dashboard
Server ComponentsとClient Componentsの使い分け

App Routerで最も重要な設計判断が、Server ComponentsとClient Componentsのどちらを使うかです。
判断基準:この表で即決できる
| 必要な機能 | 使うべきコンポーネント |
|---|---|
| データベース・API から直接データ取得 | Server Component |
| 環境変数(シークレット)にアクセス | Server Component |
useState / useReducer で状態管理 |
Client Component |
useEffect でライフサイクル処理 |
Client Component |
onClick / onChange などのイベントハンドラ |
Client Component |
ブラウザAPI(localStorage / window)を使用 |
Client Component |
| カスタムフック(状態・副作用あり)を使用 | Client Component |
原則は「Server Componentをデフォルトにし、必要な箇所だけClient Componentにする」です。
Server Componentの実装例
// app/blog/[slug]/page.tsx
// "use client" なし → Server Componentとして動作
import { getPost } from '@/lib/api'
// async/await で直接データ取得できる
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug) // DBやAPIに直接アクセス可能
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
Client Componentの実装例
// components/like-button.tsx
"use client" // ファイル冒頭に宣言
import { useState } from 'react'
export function LikeButton({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount)
return (
<button onClick={() => setCount(c => c + 1)}>
いいね {count}
</button>
)
}
Client Boundaryの設計ミスを防ぐ
"use client" を宣言したコンポーネントは、その子コンポーネント全体がクライアントサイドに引き込まれます。これを「Client Boundary」と呼びます。
特に危険なのが app/layout.tsx 直下での "use client" 宣言です。ルートレイアウトをClient Componentにすると、アプリ全体がクライアントバンドルに含まれ、App Routerの恩恵がほぼ消えます。
Client Componentはコンポーネントツリーの末端(葉)に配置するのが鉄則です。
// ❌ 悪い例:親コンポーネントをClient Componentにする
"use client"
export default function Dashboard() {
const [tab, setTab] = useState('overview')
return (
<div>
<TabBar tab={tab} onChange={setTab} />
<HeavyDataComponent /> // 本来サーバーで動かせるのにクライアントに引き込まれる
</div>
)
}
// ✅ 良い例:インタラクションが必要な部分だけを切り出す
// components/tab-bar.tsx
"use client"
export function TabBar({ tab, onChange }: TabBarProps) {
return <nav>...</nav>
}
// app/dashboard/page.tsx(Server Component)
import { TabBar } from '@/components/tab-bar'
import { HeavyDataComponent } from '@/components/heavy-data'
export default async function Dashboard() {
return (
<div>
<TabBar /> {/* Client Component(インタラクション担当)*/}
<HeavyDataComponent /> {/* Server Component(データ取得担当)*/}
</div>
)
}
データフェッチとキャッシュ戦略
App Routerでは getServerSideProps / getStaticProps が廃止され、コンポーネント内で直接 async/await を使ったデータ取得が標準になりました。
fetch APIのキャッシュ制御
// デフォルト:リクエスト結果をキャッシュ(静的データ向け)
const data = await fetch('https://api.example.com/posts')
// 1時間ごとに再検証(ISR相当)
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
})
// キャッシュなし(リクエストごとに最新取得、SSR相当)
const data = await fetch('https://api.example.com/posts', {
cache: 'no-store'
})
ウォーターフォール問題を防ぐ並列フェッチ
複数の非依存データは Promise.all で並列取得します。直列で書くと合計待機時間が掛け算になります。
export default async function ProductPage({ params }: { params: { id: string } }) {
// ❌ 直列(合計: 300ms + 200ms = 500ms)
// const product = await getProduct(params.id)
// const reviews = await getReviews(params.id)
// ✅ 並列(合計: max(300ms, 200ms) = 300ms)
const [product, reviews] = await Promise.all([
getProduct(params.id),
getReviews(params.id)
])
return (
<main>
<ProductDetail product={product} />
<ReviewList reviews={reviews} />
</main>
)
}
Server Actions:フォーム処理をシンプルに
Next.js 15で安定化したServer Actionsを使うと、APIルートを書かずにフォーム送信のサーバー処理を実装できます。
// app/contact/page.tsx
export default function ContactPage() {
async function submitContact(formData: FormData) {
"use server" // Server Action の宣言
const email = formData.get('email') as string
const message = formData.get('message') as string
await saveContactToDb({ email, message })
}
return (
<form action={submitContact}>
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">送信</button>
</form>
)
}
Pages Routerから App Routerへの移行手順
既存のPages RouterプロジェクトをApp Routerに移行する場合、段階的な移行が公式推奨です。pages/ と app/ は共存できるため、一度にすべてを移行する必要はありません。
ステップ1:`app/` ディレクトリの作成とルートレイアウト追加
// app/layout.tsx(必須:HTMLのルート構造を定義)
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="ja">
<body>{children}</body>
</html>
)
}
_app.js と _document.js に書いていたグローバルの設定は、app/layout.tsx に移行します。
ステップ2:ルーターのインポート先を変更
// Before(Pages Router)
import { useRouter } from 'next/router'
const router = useRouter()
router.push('/dashboard')
// After(App Router)
import { useRouter } from 'next/navigation' // インポート先が変わる
const router = useRouter()
router.push('/dashboard')
ステップ3:データフェッチ関数の置き換え
// Before(Pages Router)
export async function getServerSideProps() {
const data = await fetchData()
return { props: { data } }
}
export default function Page({ data }: { data: Data }) {
return <div>{data.title}</div>
}
// After(App Router)
export default async function Page() {
const data = await fetchData() // コンポーネント内で直接フェッチ
return <div>{data.title}</div>
}
ステップ4:影響が少ないページから順番に移行
移行の優先順位は次の通りです。
- 静的なページ(会社概要・プライバシーポリシー等)
- 管理画面の一部
- ブログ・記事一覧・詳細ページ
- 認証が必要なページ
- コア機能のページ(慎重に最後に)
ステップ5:よくある移行時のエラーと対処
| エラー | 原因 | 対処 |
|---|---|---|
You're importing a component that needs useState |
Server Componentで useState を使用 |
ファイル冒頭に "use client" を追加 |
useRouter が動作しない |
next/router を使っている |
next/navigation に変更 |
cookies() / headers() でエラー |
静的レンダリング中に動的APIを呼んでいる | export const dynamic = 'force-dynamic' を追加 |
| レイアウトが二重に適用される | _app.js と app/layout.tsx が共存 |
_app.js のグローバル設定を layout.tsx に移行後に削除 |
Next.js 15・16の最新機能【2026年版】
2026年時点でのNext.js最新機能を把握しておくことで、App Routerをより効果的に活用できます。
Turbopackが標準バンドラに(Next.js 16)
Next.js 16からTurbopackがstable(安定版)となり、デフォルトバンドラとして採用されました。Webpackと比べて次のようなパフォーマンス改善が報告されています。
- ローカル開発サーバーの起動:最大76%高速化
- コードの変更反映(HMR):最大96%高速化
- ファイルシステムキャッシュにより、再起動後もコンパイル結果を再利用
特別な設定不要で、next dev と next build の両方でTurbopackが動作します。
Partial Pre-Rendering(PPR)が安定化(Next.js 16)
Partial Pre-Rendering(PPR)は、1つのルートを「静的部分」と「動的部分」が混在できる新しいレンダリングモデルです。Next.js 16で安定版として利用可能になりました。
// app/product/[id]/page.tsx
import { Suspense } from 'react'
import { ProductDetails } from './product-details' // 静的(CDNキャッシュ)
import { StockStatus } from './stock-status' // 動的(リクエスト毎に取得)
export default function ProductPage() {
return (
<div>
<ProductDetails /> {/* 静的:CDNからすぐ返る */}
<Suspense fallback={<p>在庫確認中...</p>}>
<StockStatus /> {/* 動的:ストリーミングで遅延配信 */}
</Suspense>
</div>
)
}
静的シェルをCDNから瞬時に返しつつ、動的な「穴」だけをストリーミングで後から埋める仕組みです。ECサイトの商品詳細ページ(静的な商品説明+動的な在庫状況)などに特に効果的です。
`after()` APIでアナリティクスをノンブロッキングに(Next.js 15+)
import { after } from 'next/server'
export default async function Page() {
after(() => {
// レスポンス送信後に非同期で実行(PV記録・ログ等)
recordPageView()
})
return <main>...</main>
}
after() を使うと、ユーザーへのレスポンスをブロックせずにアナリティクス記録やログ送信を行えます。
よくある質問(FAQ)
App RouterとPages Routerはどちらを選ぶべきですか?
新規プロジェクトはApp Routerを選択してください。Next.js公式がApp Routerへの移行を推奨しており、新機能(PPR・Server Actions・Turbopack)はすべてApp Routerに対応しています。
既存プロジェクトの場合は費用対効果を見極めてください。pages/ と app/ は共存できるため、段階的な移行が可能です。
`"use client"` はどこに書くべきですか?
"use client" はファイルの最初の行に書きます(コメントより前でも後でも可)。インポートより前に置く必要があります。
また、"use client" を書くのは**useState・useEffect・イベントハンドラを直接使うファイルのみ**にしてください。子コンポーネントは自動的にClient Componentとして扱われるため、すべてのファイルに書く必要はありません。
Server ComponentsからClient Componentsにデータを渡す方法は?
PropsとしてのJSON-serializableなデータを渡すのが基本です。
// Server Component(データ取得)
export default async function ProductPage() {
const product = await getProduct()
return <ProductCard product={product} /> // Props として渡す
}
// Client Component(インタラクション担当)
"use client"
export function ProductCard({ product }: { product: Product }) {
const [liked, setLiked] = useState(false)
return <div onClick={() => setLiked(true)}>{product.name}</div>
}
PropsはシリアライズできるもののみOK(Date・Map・Setは注意が必要)。関数・クラスインスタンスは渡せません。
クライアントコンポーネント内でサーバーコンポーネントを使う方法は?
直接インポートすることはできません。children Props として渡すパターンを使います。
// Client Component
"use client"
export function Modal({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false)
return open ? <div>{children}</div> : null
}
// Server Component(使う側)
import { Modal } from './modal'
import { HeavyServerData } from './heavy-server-data'
export default function Page() {
return (
<Modal>
<HeavyServerData /> {/* Server Componentとして動作する */}
</Modal>
)
}
`layout.tsx` と `template.tsx` の違いは?
layout.tsx はナビゲーション間で状態が維持され再レンダリングされません。template.tsx はページ遷移のたびに新しいインスタンスが生成されます。
通常は layout.tsx を使い、ページ遷移のたびにリセットが必要なアニメーションや状態管理がある場合のみ template.tsx を使います。
まとめ:App Router移行で得られるもの
Next.js App Routerは、React Server Componentsを中核に据えた次世代のWebアプリケーションアーキテクチャです。2026年現在、Next.js 16でTurbopackとPPRが安定版になり、App Routerの完成度はさらに高まっています。
- バンドルサイズ削減:Server Componentsによりクライアントに送るJSを最小化
- 柔軟なレイアウト管理:ネスト可能な
layout.tsxでルートごとにUIを制御 - 直感的なデータフェッチ:
async/awaitをコンポーネント内で直接使用 - 段階的な移行:
pages/とapp/の共存で既存プロジェクトもリスクなく移行
Next.jsとReactの選定基準については Next.jsとReactの違いを徹底解説【2026年版】 を、React Server Componentsの詳細な仕組みについては React Server Componentsとは?仕組みとメリットを初心者向けに徹底解説 をあわせてご覧ください。App RouterをVercelにデプロイする際は Vercelとは?仕組み・料金・使い方を初心者向けに完全解説【2026年版】 も参考になります。
この記事を書いた人

コセケン
テクラル合同会社
スタートアップでのCTO経験を経て、現在はテクラル合同会社にてシステム開発全般を牽引しています。アプリおよびWebの開発から、バックエンド、インフラ構築に至るまで幅広い技術領域に対応可能です。スピード感を持った品質の高いシステム開発を得意としており、新規プロダクトの立ち上げを一気通貫で支援します。本ブログでは実践的な開発ノウハウを発信していきます。


