Featured image of post Next.js App Routerの基本と導入ポイントFeatured image of post Next.js App Routerの基本と導入ポイント

Next.js App Routerの基本と導入ポイント

Next.js App Routerの基本と導入ポイントを解説。ルーティング設計を理解したい人向けに整理します。

こんにちは!Next.js 13以降で導入されたApp Routerは、従来のPages Routerとはまったく異なるアプローチでルーティングやレンダリングを管理します。すでに安定版として広く使われているApp Routerの基本を、これから移行する方向けに解説します。

App Router vs Pages Routerの違い

項目Pages RouterApp Router
ルーティングpages/ 配下app/ 配下
コンポーネントデフォルトでClient ComponentデフォルトでServer Component
レイアウト個別に実装ネスト可能な layout.js
ローディング手動実装loading.js で自動
データ取得getServerSideProps などコンポーネント内で直接 async

1. ファイルベースルーティングの基本

app/ ディレクトリ内のフォルダ構造がそのままURLパスになります。

app/
├── page.js          → /
├── layout.js        → 全ページ共通レイアウト
├── about/
│   └── page.js      → /about
└── blog/
    ├── page.js      → /blog
    └── [slug]/
        └── page.js  → /blog/hello-world(動的ルート)
// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  return <article>{post.title}</article>;
}

2. レイアウトシステム

layout.js はそのフォルダ以下の全ページに自動で適用されます。ネストも可能です。

// app/layout.js(ルートレイアウト)
export default function RootLayout({ children }) {
  return (
    <html lang="ja">
      <body>
        <header>サイト共通ヘッダー</header>
        {children}
        <footer>サイト共通フッター</footer>
      </body>
    </html>
  );
}
// app/blog/layout.js(ブログセクションのみのレイアウト)
export default function BlogLayout({ children }) {
  return (
    <div className="blog-wrapper">
      <aside>サイドバー</aside>
      <main>{children}</main>
    </div>
  );
}

3. ローディングとエラー状態

loading.jserror.js を配置するだけで、ローディングUIとエラーUIを自動的に表示できます。

// app/blog/loading.js
export default function Loading() {
  return <div>読み込み中...</div>;
}
// app/blog/error.js
'use client';
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <button onClick={reset}>再試行</button>
    </div>
  );
}

4. Server ComponentとClient Componentの使い分け

App Routerでは、デフォルトがServer Componentです。これは重要な変更点です。

Server ComponentClient Component
レンダリング場所サーバーブラウザ
useState / useEffect❌ 使えない✅ 使える
データベース直接アクセス✅ 可能❌ できない
ファイルサイズ小さい(JS不送)大きい
インタラクション❌ できない✅ できる
// これは Server Component(デフォルト)→ DBアクセスできる
export default async function PostList() {
  const posts = await db.query('SELECT * FROM posts');
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
// クライアント側でインタラクションが必要な場合のみ 'use client'
'use client';
export default function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(!liked)}></button>;
}

基本方針: できるだけServer Componentで書き、インタラクションが必要な最小限のコンポーネントだけClient Componentにします。

5. データフェッチングのパターン

App Routerでは fetch() をコンポーネント内で直接呼び出せます。デフォルトでキャッシュされます。

// キャッシュあり(デフォルト)
const data = await fetch('https://api.example.com/posts');

// キャッシュなし(毎回再取得)
const data = await fetch('https://api.example.com/posts', {
  cache: 'no-store',
});

// 一定時間で再検証
const data = await fetch('https://api.example.com/posts', {
  next: { revalidate: 60 }, // 60秒ごとに再検証
});

6. Route Groups(ルートグループ)

( ) でフォルダ名を囲むと、URLに影響を与えずにファイルを整理できます。

app/
├── (marketing)/
│   ├── page.js      → /
│   └── pricing/
│       └── page.js  → /pricing
└── (shop)/
    ├── layout.js    → ショップ専用レイアウト
    └── products/
        └── page.js  → /products

マーケティングサイトとショップで異なるレイアウトを使いたい場合などに便利です。

まとめ

App RouterはServer Componentを軸にした新しいアーキテクチャで、従来のPages Routerとは考え方が大きく異なります。最初は混乱するかもしれませんが、「Server Componentを基本に、必要な部分だけClient Component」という方針で書いていけば、パフォーマンスの高いアプリケーションが作れます。移行は小さなページから徐々に進めるのがおすすめです。

今日も最後までお読みいただき、ありがとうございました!