Next.js 13 App Router入門:Pages Routerとの違いと移行のコツをわかりやすく紹介

Next.js 13 App Router入門:Pages Routerとの違いと移行のコツについて何回かに分けて紹介していこうと思います。
革新的なルーティング構造の登場
Next.js 13から導入されたApp Routerは、これまでのPages Routerとは異なる新たなルーティング方式です。
React 18のServer ComponentsやStreamingに完全対応し、より柔軟かつ高速なアプリケーション構築を可能にします。
しかし、新しい概念が多いため、「何が変わったのか」「どうやって移行すれば良いのか」と迷われている方も多いのではないでしょうか。
本記事では、初心者の方にもわかりやすく、App RouterとPages Routerの違いや移行方法を、実際のコードを交えて丁寧に解説してまいります。
App Routerとは何か?
まずは、App Routerの概要を押さえましょう。
Next.jsの公式ドキュメントでは以下のように説明されています:
The App Router introduces a new routing system built on top of React Server Components, supports layouts, and provides enhanced flexibility.
Next.js App Router Docs
要点をまとめると、App Routerは以下の特徴を持っています。
特徴 | 内容 |
---|---|
ディレクトリベースのルーティング | /app 以下にフォルダとファイルを配置することでルーティングを定義 |
Layoutによる共通UI構造 | 各ページで共通レイアウトを容易に適用可能 |
Server Component対応 | パフォーマンス向上のために、デフォルトでサーバーサイドでレンダリング |
Streaming対応 | クライアントに逐次レンダリングして表示速度を向上 |
File Conventionの刷新 | page.tsx , layout.tsx , loading.tsx などファイルによって意味が決定する |
では、これらの特徴をそれぞれ詳しく見ていきましょう。
ディレクトリ構造の違い
Pages Routerの場合
従来のPages Routerでは、ルーティングはpages/
ディレクトリ内に.tsx
ファイルを配置することで構成されていました。
例:Pages Routerの構成
bash/pages
├─ index.tsx → ルート (/)
├─ about.tsx → /about
└─ blog/
└─ [id].tsx → 動的ルーティング (/blog/1など)
この構成ではReactのコンポーネントとURLの関係が直感的ですが、レイアウトの共有やサーバーコンポーネントの導入には限界がありました。
App Routerの場合
App Routerでは、すべてのルート定義が/app
ディレクトリで構成されます。
例:App Routerの構成
bash/app
├─ page.tsx → ルート (/)
├─ about/
└─ page.tsx → /about
├─ blog/
├─ [id]/
│ └─ page.tsx → 動的ルーティング (/blog/1)
└─ layout.tsx → blog配下の共通レイアウト
これにより、ディレクトリ単位でルートやレイアウトを柔軟に管理でき、モジュール化された設計が可能になります。
page.tsx
の基本
App Routerでは各ルートのページコンポーネントは必ずpage.tsx
として定義します。
例:基本的なページ定義
tsx// app/about/page.tsx
export default function AboutPage() {
return <h1>Aboutページです</h1>;
}
このファイルを作成するだけで、/about
というURLが自動的に生成されます。
とてもシンプルで、Pages Routerに慣れている方でも違和感なく使い始められるでしょう。
layout.tsx
で共通レイアウトを実現
App Routerでは、レイアウトを共通化する際にlayout.tsx
というファイルを使います。
このファイルはディレクトリ単位で機能し、配下のすべてのルートに適用されます。
例:共通レイアウトの実装
tsx// app/blog/layout.tsx
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<h2>Blogページ共通の見出し</h2>
<main>{children}</main>
</div>
);
}
これにより、/blog/
以下のすべてのページでこのレイアウトが使用されます。
loading.tsx
によるローディングUI
App Routerではページの読み込み中に表示されるローディングUIをloading.tsx
で定義できます。
例:ローディング画面の定義
tsx// app/blog/loading.tsx
export default function Loading() {
return <p>読み込み中です...</p>;
}
ルートの読み込みが完了するまでこのUIが表示されるため、ユーザー体験の向上につながります。
動的ルーティングと[param]
の使い方
App Routerでも、Pages Routerと同様に動的ルーティングが可能です。ファイル名を角括弧で囲うことで定義します。
例:動的ルートの定義
tsx// app/blog/[id]/page.tsx
type Props = {
params: { id: string }
};
export default function BlogPost({ params }: Props) {
return <h1>ブログ記事 ID: {params.id}</h1>;
}
params.id
によりURLパラメータが取得できます。getServerSideProps
やgetStaticProps
は不要です。
Server ComponentとClient Componentの違いと使い分け
Next.js 13 App Routerでは、コンポーネントがサーバーで実行されるか、クライアントで実行されるかを明示的に区別する必要があります。
この考え方は、パフォーマンスやセキュリティ、レンダリング戦略に直結するため、非常に重要です。
Server Componentの特徴
Server Componentはデフォルトで有効です。つまり、特別な指定をしなければ、そのコンポーネントはサーバー側でレンダリングされます。
利点
- APIやDBに直接アクセス可能(Node.jsランタイムで動作)
- 初回描画が高速(HTMLとして返却)
- クライアントのバンドルサイズを削減可能
制約
useState
,useEffect
などのクライアント専用Hooksは使用不可- ブラウザ専用のAPI(
window
など)も使用不可
例:Server Component
tsx// app/dashboard/page.tsx
async function getData() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
return res.json();
}
export default async function DashboardPage() {
const post = await getData();
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
このように、fetch()
をそのまま使い、await
でデータを取得しつつHTMLを返せます。
Client Componentの指定方法
Client Componentとして実行したい場合は、ファイルの先頭に次のように記述します。
tsx'use client';
用途
- 状態管理(
useState
,useReducer
) - イベントハンドリング(ボタンクリックなど)
- クライアントサイドのインタラクション
例:Client Componentのカウンター
tsx// app/components/Counter.tsx
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
このように、useState
などのフックを使いたい場合は明示的にClient Componentにする必要があります。
Server Component × Client Componentの組み合わせ
App Routerでは、Server Component内にClient Componentを埋め込むことが可能です。
例:親はサーバー、子はクライアント
tsx// app/page.tsx(Server Component)
import Counter from './components/Counter';
export default function HomePage() {
return (
<div>
<h1>ホームページ</h1>
<Counter />
</div>
);
}
この構成により、非インタラクティブな部分はサーバーで高速に描画し、必要な箇所のみをクライアントで動的に扱うといった設計が可能になります。
metadataによるSEO設定の刷新
App Routerでは、<Head>
タグを直接記述する代わりに、metadata
オブジェクトを使ってSEO情報を設定します。
これは静的なメタ情報生成にも対応しており、SSR/SSGのどちらにも適応できる柔軟な方式です。
静的なmetadataの例
tsx// app/about/page.tsx
export const metadata = {
title: 'Aboutページ | MySite',
description: 'このページはMySiteのAboutページです。',
};
export default function AboutPage() {
return <h1>About</h1>;
}
このようにmetadata
をエクスポートするだけで、HTMLの<title>
や<meta name="description">
タグが生成されます。
動的にmetadataを生成する方法
動的ルートで記事タイトルなどを反映させたい場合は、generateMetadata()
関数を使用します。
例:記事ごとのメタ情報設定
tsx// app/blog/[id]/page.tsx
export async function generateMetadata({ params }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
const post = await res.json();
return {
title: `${post.title} | ブログ`,
description: post.body.slice(0, 100),
};
}
詳細は公式の以下ドキュメントも併せてご覧ください: https://nextjs.org/docs/app/api-reference/functions/generate-metadata
generateStaticParamsによる静的生成(SSG)
App Routerでは、SSG対応としてgetStaticPaths
の代わりにgenerateStaticParams
を使用します。
これにより、ビルド時に特定のパスで静的ページを生成できます。
例:/blog/1
, /blog/2
などを静的生成
tsx// app/blog/[id]/page.tsx
export async function generateStaticParams() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
return posts.slice(0, 5).map((post) => ({
id: post.id.toString(),
}));
}
これにより、該当するページは事前に生成され、表示が高速になります。
フォーム送信とAPI処理のパターン
App Routerでは、従来のAPI Routes
(pages/api/
)に加え、Server Actions
を活用することで、より直感的かつセキュアなフォーム送信処理が可能になります。
クライアントサイドのフォーム送信(fetch + API Routes)
従来通り、fetch
を用いて/api
エンドポイントにPOSTする方式も利用可能です。
例:シンプルなフォーム送信(クライアント側)
tsx'use client';
import { useState } from 'react';
export default function ContactForm() {
const [message, setMessage] = useState('');
const [status, setStatus] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify({ message }),
headers: {
'Content-Type': 'application/json',
},
});
if (res.ok) {
setStatus('送信されました');
} else {
setStatus('送信に失敗しました');
}
};
return (
<form onSubmit={handleSubmit}>
<textarea value={message} onChange={(e) => setMessage(e.target.value)} />
<button type="submit">送信</button>
<p>{status}</p>
</form>
);
}
対応するAPIエンドポイント(pages/api/contact.ts)
ts// pages/api/contact.ts
export default function handler(req, res) {
if (req.method === 'POST') {
console.log('受信したメッセージ:', req.body.message);
res.status(200).json({ status: 'ok' });
} else {
res.status(405).end(); // Method Not Allowed
}
}
Server Actionsによるフォーム送信
Next.js 13では、server action
という新しい仕組みが導入されました。フォームに直接action
属性として非同期関数を渡すことで、サーバーサイドで処理を行えます。
例:Server Actionsを使った送信処理
tsx// app/actions/submitMessage.ts
'use server';
export async function submitMessage(formData: FormData) {
const message = formData.get('message')?.toString();
console.log('送信内容:', message);
}
tsx// app/contact/page.tsx
import { submitMessage } from '../actions/submitMessage';
export default function ContactPage() {
return (
<form action={submitMessage}>
<textarea name="message" />
<button type="submit">送信</button>
</form>
);
}
この形式では、クライアントでfetchを書く必要がなく、サーバーで直接処理されるためセキュリティも高まります。
詳しくは公式ガイドも参考ください: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
Pages RouterからApp Routerへの移行戦略
App Routerは新しく強力な仕組みですが、既存のプロジェクトを完全移行するのは大きな作業になります。
Next.jsではPages RouterとApp Routerの共存が可能なため、段階的な移行が現実的です。
共存可能な構成例
bash/app
└─ page.tsx → App Routerで構成
/pages
└─ contact.tsx → 従来のPages Routerがそのまま動作
どちらのルートも有効で、同名のルートがある場合は/app
が優先されます。
段階的な移行手順の例
ステップ | 内容 |
---|---|
1 | /app ディレクトリを新規作成 |
2 | 共通レイアウト部分(layout.tsx )を構築 |
3 | 一部ページをapp/ 配下に移動 |
4 | metadata やServer Component を導入 |
5 | pages/ ディレクトリの削除(完全移行) |
App Routerでの認証処理のパターン
App Routerでの認証は、ミドルウェア・Server Component・useSession
フックなどを組み合わせて実装可能です。
例として、next-auth
をApp Routerで使用するケースをご紹介します。
認証ミドルウェアの導入
ts// middleware.ts
import { withAuth } from 'next-auth/middleware';
export default withAuth({
pages: {
signIn: '/login',
},
});
export const config = {
matcher: ['/dashboard/:path*'], // 保護したいルート
};
この設定により、/dashboard
以下にアクセスする際はログインが必要になります。
サーバー側でのセッション取得
tsx// app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
return <p>ログインしてください</p>;
}
return <p>{session.user?.name}さん、ようこそ!</p>;
}
このように、App Routerでも柔軟な認証処理が実現可能です。
よくあるエラーとその対処方法
App Routerを導入するにあたって発生しやすいエラーと、その解決法をまとめます。
エラー内容 | 原因と対処法 |
---|---|
useState を使ったらエラーになる | 'use client' が未指定。クライアントコンポーネントに明示指定 |
window is not defined エラー | サーバー側で実行されている。Client Componentで処理する |
children が undefined になる | layout.tsx で {children} を必ず受け取る構造にする |
fetch のURLが不正 | localhost ではなく絶対URLかNext.jsのrevalidate 戦略を検討 |
まとめ
Next.js 13のApp Routerは、Reactの最新仕様を活かした強力なルーティング機構です。
ディレクトリ構造によるルーティング定義、Server Componentsとの統合、レイアウトの柔軟な共有、metadataベースのSEO対策、Server Actionsによるフォーム送信など、現代的なWebアプリ構築に不可欠な機能が数多く取り入れられています。
移行は段階的に行えるため、既存のPages Routerプロジェクトを徐々に置き換えることも可能です。
ぜひこの機会にApp Routerの導入をご検討ください。
記事Article
もっと見る- article
Dockerfileの基本構文の紹介!Dockerfile内でよく使う命令まとめ!
- article
React Suspenseを使う際に避けたいアンチパターン5選と解決策について紹介
- article
React SuspenseとServer Componentsの融合:クライアントとサーバの役割分担
- article
Suspense + useTransitionで滑らかなUXを実現するやり方を紹介
- article
React Suspenseでデータフェッチ!fetchでは動かない理由と正しい書き方について紹介
- article
React 18のSuspense完全対応ガイド:並列レンダリング時代の新常識