Next.js Pages Router と App Router、ごちゃ混ぜになりがちな違いを整理する

  • column

Web開発フレームワークとして多くの支持を集める Next.js は、バージョン13で新しい App Router がデフォルトのルーティングシステムとして導入されました。

Pages Router と App Router の両方のプロジェクトに関わる中で「どっちの書き方だっけ?」と混同する方もいらっしゃるかと思います。まさに私です!!

特に、ファイル構造に基づいたルーティングの定義方法から始まり、URLの動的な部分の扱い方、さらにはレンダリングの仕組み、そしてAPIからのデータ取得といった、アプリケーション開発の根幹に関わる部分で、Pages Router と App Router には無視できない違いが存在します。

「ごちゃ混ぜになりがちな」ポイントに焦点を当て、Pages Router と App Router の主要な違いを簡単にではありますが備忘録として記載します!

ファイルのルーディングの違い

Pages Router:ファイル名がそのままパスになるシンプルさ

pagesディレクトリ配下に配置した .js, .jsx, .ts, .tsx ファイルのファイル名が、そのままURLのパスになるというシンプルさです。

例:src/page/about.tsx ⇒ ルーティングは、/about となります。

App Router:フォルダと特定のファイル名で構造化

App Router はsrc/app配下にフォルダやファイルを置く事で、ルーティングできます。

ただし、ルーティングに利用するファイル名にpageという特定のファイル名をつけたものが、そのパスに対応するページのコンテンツとして扱われます。

例:src/app/about/page.tsx ⇒ ルーティングは、/about となります。

App Router を特徴づける「特別なファイル」たち

またApp Routerではpage.tsx以外にも、意味を持つファイルがあります。

app/
├── page.tsx           --> ページの中身。これが一番よく使うファイル
├── route.tsx          --> APIの定義(page.jsと共存不可)
├── layout.tsx         --> 共通の見た目
├── loading.tsx        --> 読み込み中の画面
├── error.tsx          --> エラー時の画面
├── global-error.tsx   --> グローバルエラー画面
├── templete.tsx       --> 共通の見た目(リセットされるレイアウト)
├── default.tsx        --> デフォルトの画面
└── not-found.tsx      --> notFound関数がスローされたときの画面

個人的に驚いたのがnot-found.tsx。このページを用意しておくだけで存在しないURLへのアクセス時に自動的に表示されるようになります。こんなに簡単に実装できてしまうのか、、と感心しました。

レンダリングの違い

Pages Router におけるコンポーネントは、基本的にはクライアントサイドでインタラクティブになることを前提としており、サーバーサイドでのレンダリングは主に初期表示の高速化やSEOのために行われます。useStateuseEffect といった React のフックは、ページコンポーネント内やその子コンポーネント内で自由に使用できます。

一方でApp Routerのapp/内のコンポーネントは、明示的に指定しない限り、デフォルトでサーバーコンポーネントとして扱われます。そのためuseStateuseEffect といったReact のフックは使用できません。クライアント側のアクション、onClickなどのイベントハンドラも使用できません。

そこでApp RouterでCSRにするにはuse clientを記述します。

use client

Server Component と Client Component の境界の宣言です。use clientを宣言したらそのコンポーネントだけではなく、インポートしてるすべても Client Component としてみなされる

動的ルーディング [○○]

ブログやニュースの記事詳細ページのように、URLの一部がユーザーやコンテンツによって変化する「動的なURL」に対応するための仕組みが、Next.js のダイナミックルーティングです。

例えば記事A、記事Bに遷移するリンクがあるとします。

例えば記事A、記事Bに遷移するリンクが並ぶページのキャプチャ
import Link from "next/link";
const linkStyle=`ext-lg font-bold border-b border-primary px-1`

export default function page() {
  return (
    <div>
      <div className="flex gap-6">
        <Link href="/post/A" className={linkStyle}>記事Aへのリンク</Link>
        <Link href="/post/B" className={linkStyle}>記事Bへのリンク</Link>
      </div>
    </div>
  );
}

リンクそれぞれの遷移先

post/A ページのキャプチャ
post/B ページのキャプチャ

動的ルーディングにする場合、[slug]というファイルを作成すれば、post/Aでもpost/Bでも同じテンプレートを使用することができます。
※[ ]のネーミングは任意なのでslugでなくてもよい

App Routerの場合

/app/post/[slug]/page.tsx

export default aysinc function PostPage({ params }: { params: Promise<{ slug: string }>) {
  const {slug}= await params;
  return (
    <div>
      <h1>記事: {slug}</h1>
    </div>
  );
}

paramsを使います。params.slug に URLの[slug]部分が入ります。

params でのパラメーター取得がこれまで非同期である必要はありませんでしたが、Next.js 15からは非同期で行うように変更されました。そのため、props を Promise 型で定義する必要があるようです。

Pages Rouerの場合

/pages/post/[slug].tsx


import { useRouter } from 'next/router';

export default function PostPage() {
  const router = useRouter();
  const { slug } = router.query;

  return (
    <div>
      <h1>記事: {slug}</h1>
    </div>
  );
}

useRouterを使います。router.query.slug に、URLの [slug] の部分の値が入ります。

APIデータ取得方法

まさにこのLiberogicコラムでも大活躍しているmicroCMSはNext.jsのようなフレームワークとの相性がよいです。

App Routerの場合

シンプルにfetchaysnc awaitを使用するでよいです。

ただし、use clientを宣言したファイルはCSRになるのでaysnc awaitは使えなくなります!


async function getPost() {
  const res = await fetch(`https://.../api/data/`);

  // エラーハンドリング例
  if (!res.ok) {
     throw new Error(`Failed to fetch post: ${res.status}`);
  }

  return res.json();
}

呼び出し先

export default function Example() {
   const data = await getPost()
   // ... data を使って表示 ...
}

Pages Routerの場合

getStaticProps を使用し、取得したデータを props としてページコンポーネントに渡します。


export async function getServerSideProps(context) {
  // context.req, context.res などにアクセス可能
  const res = await fetch(`https://.../api/data/`, {
     headers: { Cookie: context.req.headers.cookie } // 例: Cookieを渡す
  });
  const data = await res.json();

  return {
    props: {
      data,
    },
  };
}

呼び出し先

export default function Example({ data }) {
  // ... data を使って表示 ...
}

二つのルーターを理解し、Next.js を使いこなそう!

私の場合、最初にNext.jsを触った案件はApp Routerでした。

その後に別の案件でPages Routerを触り、「新規のページを作成するにはファイル名 index.tsxとpage.tsxどっちだっけ??」と、なりました。

今回の内容はNext.jsのほんの一部ですが、改めてApp RouterとPages Rouerの違いなどを調べることで自分の中でも整理できました。

個人的にはApp Routerの書き方が分かりやすくて好きなのですが、Pages Routerの案件でもしっかり対応できるようにしたいです。

Next.js をより深く理解し案件や制作物によってそれぞれのルーターを使い分けられるようになるのが理想ですね!

この記事を書いた人

マークアップを中心に、JavaScriptやReact、Next.jsを使ってフロントエンドの開発をやっています。自分が関わったサイトが無事に公開されると嬉しかったりします!趣味はギターを弾くこと。コードは書くのも弾くのもどっちも楽しいです!

ひらっち

フロントエンドエンジニア/2022年入社

安心のチーム体制とスピードのある対応力が自慢

リベロジックでは、ベテランスタッフが積極的にプロジェクトを推進するため、お客様から高く評価されています。
プロジェクトマネージャー、ディレクターをきちんとアサインし、プロジェクト全体をスムーズに進行することを心掛けています。 不必要なフルコミットでのコスト増加を防ぎ、適材適所にリソースを配分するスタイルで、業務内容の把握から見積作成/提出の速さにも定評があります。

当社はSES的な常駐業務等は積極的に行っておりませんので予めご了承ください。

Slack、Teams、Redmine、Backlog、Asana、Jira、Notion、Google Workspace、Zoom、Webexなど、ほぼすべての主要なプロジェクト管理ツールやチャットツールをご利用いただけます。

SESやオフショアを利用する大規模案件において、技術的な課題や取り組み方にお悩みはございませんか?

ケーススタディ