投稿日:2026/7/28
更新日:2026/7/28

app/)は React Server Components(RSC)を中心とした現在の標準。Next.js 13 以降で追加され、公式ドキュメントも新機能もこちらが前提。pages/)は Next.js 12 以前からの従来型。今も動くが、新機能は基本的に来ない。Pages Router はファイル名がそのまま URL、App Router はフォルダ名が URL で中の page.tsx が描画する。
| Pages Router | App Router | |
|---|---|---|
| 静的ページ | pages/about.tsx |
app/about/page.tsx |
| 動的ルート | pages/blog/[id].tsx |
app/blog/[id]/page.tsx |
| API | pages/api/users.ts |
app/api/users/route.ts |
App Router は「フォルダ = ルートセグメント」なので、そのセグメントに関係するファイル(loading.tsx、error.tsx、テストなど)を同じフォルダに同居させられる(コロケーション)。page.tsx が無いフォルダは URL にならないので、app/(admin)/ のようなグルーピングも可能。
ここが最大のアーキテクチャ変更。
useState / onClick などインタラクションが必要なものだけ 'use client' を宣言する。// app/products/page.tsx — Server Component(デフォルト)
// DB クライアントや重いライブラリはここで完結し、ブラウザには送られない
import { db } from '@/lib/db'
import { AddToCartButton } from './add-to-cart-button'
export default async function Page() {
const products = await db.product.findMany()
return (
<ul>
{products.map((p) => (
<li key={p.id}>
{p.name}
<AddToCartButton id={p.id} />
</li>
))}
</ul>
)
}
// app/products/add-to-cart-button.tsx
'use client' // ← インタラクションがあるのでここだけクライアント
import { useState } from 'react'
export function AddToCartButton({ id }: { id: string }) {
const [added, setAdded] = useState(false)
return <button onClick={() => setAdded(true)}>{added ? '追加済み' : 'カートに追加'}</button>
}
'use client' はそのファイル以下の import ツリー全体がクライアント境界に入る点に注意。ページの一番上に付けると App Router の恩恵がほぼ消える。
_app.tsx / _document.tsx。ページごとのネストしたレイアウトは getLayout パターンなど自前の工夫が必要だった。layout.tsx を置くだけで自動的にネストされる。ページ遷移しても変化しない親レイアウトは再レンダリングされない(state も DOM も保持される)。app/
├── layout.tsx ← 全ページ共通(html/body はここ)
└── dashboard/
├── layout.tsx ← /dashboard/* 共通のサイドバー
├── page.tsx
└── settings/page.tsx ← 上2つの layout に囲まれる
Next.js 独自関数が廃止され、標準的な async/await に一元化された。
// Pages Router: 独自の特殊関数でページ単位で取得し props で流す
export const getServerSideProps = async () => {
const res = await fetch('https://api.example.com/posts')
return { props: { posts: await res.json() } }
}
// App Router: コンポーネント内で直接 await する。キャッシュ設定で SSR/SSG/ISR を切り替え
export default async function Page() {
const res = await fetch('https://api.example.com/posts', {
// cache: 'no-store' → 毎リクエスト取得(SSR 相当)
next: { revalidate: 60 }, // → 60秒ごとに再生成(ISR 相当)
})
const posts = await res.json()
return <PostList posts={posts} />
}
ページの入口でまとめて取る必要がなくなり、データを使うコンポーネントの中で取る書き方ができるのが大きい。
App Router は機能ごとに予約ファイル名が用意されている。詳細は ファイル名ベース仕様メモ(App Router) を参照。
| ファイル | 役割 |
|---|---|
page.tsx |
メイン画面 |
layout.tsx |
共通レイアウト |
loading.tsx |
取得中のローディング UI(Suspense 連携) |
error.tsx |
エラー時のフォールバック |
not-found.tsx |
404 UI(not found) |
route.ts |
API Route Handler |
| 項目 | Pages Router | App Router |
|---|---|---|
| 位置づけ | 従来型(レガシー) | 現在の標準(Next.js 13〜) |
| ベースフォルダ | pages/ |
app/ |
| デフォルト | クライアント指向 | Server Component(RSC) |
| レイアウト | _app.tsx で一括指定 |
layout.tsx で柔軟にネスト |
| データ取得 | getServerSideProps / getStaticProps |
async/await + 拡張 fetch() |
| ローディング | 自前で state 管理 | loading.tsx / Suspense |
| メタデータ(SEO) | <Head> コンポーネント |
metadata / generateMetadata |
| ストリーミング | 実質なし | 標準対応 |
クライアント側のパフォーマンス(JS 量と体感速度)では App Router が圧倒的に有利。構成次第だが、適切に移行するとブラウザに落ちる JS が 30〜50% 以上削減され、初期表示とページ遷移が目に見えて軽くなるケースが多い。
差を生む要因は 4 つ。
Pages Router では、date-fns・Markdown パーサー・グラフ描画ライブラリなどをコンポーネントで import すると、そのままクライアントバンドルに入っていた。
App Router(Server Component)ならこれらの処理をサーバーで完了させ、結果の HTML だけをブラウザに送れる。ライブラリのコード自体が配信されないので、初期ロード(FCP / LCP)とブラウザ側の JS パース・実行コストが劇的に下がる。
getServerSideProps はサーバー側で全データ取得が終わるまで HTML を 1 文字も返せない(TTFB が悪化する)。
App Router では loading.tsx / Suspense で段階的描画ができる。
ユーザーからは「すぐ反応した」と感じられるので、数値以上に体感が良くなる。
_next/data)を読み直して描画し直す。fetch の自動重複排除同一リクエスト内で、複数の深いコンポーネント(ヘッダー・アイコン・サイドバー)がそれぞれ「ログイン中ユーザー情報」を必要とするケース。App Router の拡張 fetch は同じリクエストを自動で 1 回にまとめる(Request Deduplication)ので、上からバケツリレーで props を渡す設計にしなくても無駄な通信が増えない。
'use client' の多用: 付けまくると実質 Pages Router と同じ(レイアウトのオーバーヘッド分わずかに重い)状態になり、恩恵を受けられない。await を素直に直列で書くと逐次実行になる。独立したデータは Promise.all で並列化する。pages/ と app/ は共存可能なので、新しいページや機能から app/ へ寄せていくのが公式にも推奨されているアプローチ。なお、コンテンツ閲覧中心のサイト(メディア・EC)ほど RSC の恩恵は大きく、フォーム操作が主体の管理画面・SaaS は 'use client' の比率が上がるため差は小さくなる。ただし後者でも、レイアウトのネストとストリーミングのメリットは残る。