Topics

Sorting Out the Differences Between Next.js Pages Router and App Router, Which Tend to Get Mixed Up

  • column

Next.js, a widely adopted web development framework, introduced the new App Router as its default routing system with version 13.

Many developers working on both Pages Router and App Router projects find themselves asking, "Wait, which syntax was it again?" I certainly do!!

Pages Router and App Router differ in ways that cannot be ignored at the core of application development, from how routes are defined based on file structure, to how dynamic URL segments are handled, how rendering works, and how data is fetched from APIs.

Focusing on the points that are most often confused, this post serves as a brief memo covering the key differences between Pages Router and App Router!

Differences in File-Based Routing

Pages Router: The simplicity of file names becoming paths

The simplicity lies in how file names of .js, .jsx, .ts, and .tsx files placed under the pages directory become the URL paths as they are.

For example: src/page/about.tsx → the route becomes /about.

App Router: Structured by folders and specific file names

With the App Router, you can set up routing by placing folders and files under src/app.

However, a file named specifically "page" used for routing is treated as the page content corresponding to that path.

Example: src/app/about/page.tsx → The route becomes /about.

The "Special Files" That Define the App Router

Beyond page.tsx, the App Router also includes other files that have special meaning.

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関数がスローされたときの画面

What surprised me most was not-found.tsx. Simply by creating this page, it is automatically displayed when a nonexistent URL is accessed. I was impressed that it could be implemented so easily...

Differences in Rendering

In the Pages Router, components are basically designed to be interactive on the client side, and server-side rendering is mainly used to speed up initial page display and for SEO. React hooks such as useState and useEffect can be freely used within page components and their child components.

In contrast, components inside app/ in the App Router are treated as Server Components by default unless explicitly specified. Therefore, useState and useEffect and other React hooks cannot be used. Client-side actions and event handlers such as onClick also cannot be used.

To make a component CSR in the App Router, you write use client.

use client

This is the boundary declaration between Server Components and Client Components. Once you declare use client, not only that component but also everything it imports is treated as a Client Component.

Dynamic Routing [○○]

In Next.js, dynamic routing is the mechanism for handling "dynamic URLs" where part of the URL changes depending on the user or content, such as article detail pages on a blog or news site.

For example, suppose you have links that navigate to Article A and Article B.

For example, a screenshot of a page with links to Article A and Article 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>
  );
}

The destination of each link.

Screenshot of the post/A page
Screenshot of the post/B page

To use dynamic routing, create a file named [slug], and the same template can be used for both post/A and post/B.
Note: The naming inside [ ] is up to you, so it does not have to be slug.

With the 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>
  );
}

Use params. The [slug] part of the URL is available as params.slug.

Note: Previously, reading params did not need to be asynchronous, but starting with Next.js 15 it has been changed to be asynchronous. Therefore, it seems you need to define props as a Promise type.

With the Pages Router

/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>
  );
}

Use useRouter. The value of the [slug] part of the URL is stored in router.query.slug.

How to Fetch Data from an API

microCMS, which is powering this Liberogic column, works well with frameworks like Next.js.

With the App Router

Simply using fetch with await in an async function is fine.

However, files that declare use client become CSR, so await in an async function can no longer be used!


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();
}

Caller

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

For Pages Router

Use getStaticProps to fetch data and pass the retrieved data to the page component as 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,
    },
  };
}

Caller

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

Understand both routers and master Next.js!

In my case, the first project where I used Next.js was with the App Router.

Later, while working on a different project with the Pages Router, I found myself wondering, "Was it index.tsx or page.tsx for creating a new page again??"

This article covers only a small part of Next.js, but researching the differences between the App Router and Pages Router again helped me organize my own understanding.

Personally, I like the App Router's syntax because it's easier to understand, but I want to be able to handle Pages Router projects confidently as well.

The ideal is to understand Next.js more deeply so I can choose the right router depending on the project or deliverable!

About the author

I work mainly on markup and frontend development using JavaScript, React, and Next.js. I'm always happy when a site I've been involved in launches successfully! My hobbies are playing the guitar. I love cats and roasted sweet potatoes 🐱🍠

Hirocchi

Frontend Engineer / Joined in 2022

View this staff member's articles

Reliable team structure and responsive support are our strengths

Liberogic's experienced staff actively drive projects forward, which has earned high praise from our clients.
We make sure to properly assign project managers and directors to keep the entire project running smoothly. Our style of allocating resources to the right people, rather than adding full-time commitment unnecessarily, helps prevent cost increases. We are also known for our speed, from understanding the scope of work to preparing and submitting estimates.

Note: Please be advised that we do not actively engage in SES-style on-site staffing assignments.

We support nearly all major project management and chat tools, including Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom, and Webex.

Are you struggling with technical challenges or how to approach large-scale projects that involve SES or offshore teams?

Case Studies