مفهوم App Router و تفاوت آن با Pages Router

Next.js دو سیستم مسیریابی و ساختاردهی برنامه دارد: Pages Router که قدیمی‌تر است و App Router که از نسخه‌ی ۱۳ معرفی شد و امروز روش توصیه‌شده برای پروژه‌های جدید است. چون هنوز پروژه‌ها و آموزش‌های زیادی با Pages Router وجود دارد، لازم است هر دو را بشناسید تا هنگام مراجعه به منابع مختلف دچار سردرگمی نشوید. کل این دوره فقط با App Router پیش می‌رود.

پیش‌نیاز

  • درس «ساختار فایل‌ها و پوشه‌ها»، به‌ویژه آشنایی با پوشه‌ی app
  • درس «مهم‌ترین تغییرات Next.js 16»

Router یعنی چه؟

Router بخشی از Next.js است که تعیین می‌کند «با ورود به یک آدرس، کدام فایل اجرا شود». هر دو Router از مسیریابی مبتنی بر فایل‌سیستم استفاده می‌کنند، یعنی ساختار پوشه‌ها و فایل‌ها مسیرها را مشخص می‌کند. تفاوت آن‌ها در پوشه‌ی اصلی، نام فایل‌های قراردادی، مدل کامپوننت‌ها و روش دریافت داده است.

Pages Router: مدل قدیمی

در Pages Router پوشه‌ی اصلی pages است و هر فایل داخل آن خودش یک صفحه است:

text
pages/
├── _app.tsx          ← ساختار مشترک همه‌ی صفحات
├── _document.tsx     ← تنظیم تگ‌های html و body
├── index.tsx         ← مسیر /
├── about.tsx         ← مسیر /about
├── posts/
│   ├── index.tsx     ← مسیر /posts
│   └── [slug].tsx    ← مسیر /posts/:slug
└── api/
    └── posts.ts      ← API در مسیر /api/posts

ویژگی‌های اصلی این مدل:

  • صفحات برای رندر و تعامل در مرورگر Hydrate می‌شوند. یعنی کد کامپوننت‌ها به مرورگر فرستاده می‌شود تا صفحه‌ی HTML آماده‌شده را تعاملی کند.
  • دریافت داده با توابع ویژه‌ی بیرون از کامپوننت، مثل getServerSideProps و getStaticProps، انجام می‌شود.
  • یک Layout مشترک سراسری (_app.tsx) وجود دارد و Layoutهای تودرتو نیاز به الگوهای دستی دارند.
  • APIها در پوشه‌ی pages/api نوشته می‌شوند.

App Router: مدل جدید

در App Router پوشه‌ی اصلی app است و هر پوشه یک بخش از آدرس است. فایل‌هایی با نام‌های قراردادی داخل هر پوشه تعیین می‌کنند آن بخش چه کاری انجام دهد:

text
app/
├── layout.tsx        ← Root Layout
├── page.tsx          ← مسیر /
├── about/
│   └── page.tsx      ← مسیر /about
├── posts/
│   ├── layout.tsx    ← Layout مخصوص بخش posts
│   ├── page.tsx      ← مسیر /posts
│   ├── loading.tsx   ← وضعیت انتظار
│   └── [slug]/
│       └── page.tsx  ← مسیر /posts/:slug
└── api/
    └── posts/
        └── route.ts  ← API در مسیر /api/posts

تفاوت‌های اصلی App Router:

  • Server Components به‌صورت پیش‌فرض: کامپوننت‌ها روی سرور اجرا می‌شوند و فقط در صورت نیاز با "use client" به Client Component تبدیل می‌شوند. این موضوع را در درس «Server Components و کاربرد آن‌ها» کامل می‌بینیم.
  • Layoutهای تودرتو: هر پوشه می‌تواند Layout خودش را داشته باشد و Layoutها بین صفحات مشترک می‌مانند.
  • فایل‌های ویژه برای وضعیت‌ها: loading.tsx برای انتظار، error.tsx برای خطا و not-found.tsx برای صفحه‌ی پیدا نشد.
  • دریافت داده داخل کامپوننت: کامپوننت می‌تواند async باشد و مستقیماً await کند.
  • Streaming و Suspense: بخش‌های آماده‌ی صفحه می‌توانند زودتر به کاربر برسند.
  • Server Actions و Route Handlers: برای فرم‌ها و APIها با فایل‌های route.ts.
  • Metadata API: مدیریت عنوان و توضیحات صفحه با خروجی‌های ویژه‌ی فایل، به‌جای کامپوننت next/head.

مقایسه‌ی Pages Router و App Router

موضوع Pages Router App Router
پوشه‌ی اصلی pages app
تعریف صفحه هر فایل یک صفحه است (مثل about.tsx) فایل page.tsx داخل پوشه
Layout _app.tsx و _document.tsx layout.tsx در هر سطح پوشه
مدل کامپوننت کامپوننت‌های کلاینت (با Hydration) پیش‌فرض Server Component؛ Client Component با "use client"
دریافت داده getServerSideProps، getStaticProps کامپوننت async و await
API pages/api/*.ts route.ts (Route Handlers) و Server Actions
وضعیت انتظار و خطا دستی، با State در کامپوننت‌ها (و _error برای خطاها) loading.tsx، error.tsx و Suspense
Metadata کامپوننت next/head Metadata API
Hook مسیریابی useRouter از next/router useRouter از next/navigation

مثال: فهرست نوشته‌ها در دو Router

برای دیدن تفاوت در عمل، فهرست نوشته‌های DevNotes را یک‌بار به روش Pages Router و یک‌بار به روش App Router می‌نویسیم. نمونه‌ی Pages Router فقط برای شناخت است؛ در پروژه‌ی ما ساخته نمی‌شود.

Pages Router (فقط برای شناخت)

tsx
// pages/posts/index.tsx
import type { GetServerSideProps } from "next";

type Post = { id: number; title: string };
type Props = { posts: Post[] };

export const getServerSideProps: GetServerSideProps<Props> = async () => {
  const posts: Post[] = [
    { id: 1, title: "آشنایی با Next.js" },
    { id: 2, title: "مسیریابی در App Router" },
  ];

  return { props: { posts } };
};

export default function PostsPage({ posts }: Props) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

App Router (روش این دوره)

tsx
// app/posts/page.tsx
type Post = { id: number; title: string };

async function getPosts(): Promise<Post[]> {
  return [
    { id: 1, title: "آشنایی با Next.js" },
    { id: 2, title: "مسیریابی در App Router" },
  ];
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

توضیح کد

  • در Pages Router داده در تابع جداگانه‌ی getServerSideProps گرفته می‌شود و از طریق props به کامپوننت می‌رسد. کامپوننت و تابع دریافت داده دو بخش جدا از هم‌اند.
  • در App Router خود کامپوننت async است و داده را مستقیماً با await می‌خواند؛ مسیر داده کوتاه‌تر و خواناتر است.
  • خروجی هر دو یک فهرست از عنوان نوشته‌هاست. تفاوت در محل نوشتن کد، فایل قراردادی و مدل اجرای کامپوننت است.
  • نمونه‌ی App Router همان مثال درس «تفاوت React و Next.js» است و در فصل «Rendering و Data Fetching» این الگو را با لایه‌ی دسترسی به داده کامل‌تر می‌کنیم.

چگونه تشخیص دهیم کد مربوط به کدام Router است؟

وقتی مثالی در اینترنت می‌بینید، این نشانه‌ها کمک می‌کنند:

نشانه Router مربوط
پوشه‌ی pages، فایل‌های _app.tsx و _document.tsx Pages Router
getServerSideProps یا getStaticProps یا getStaticPaths Pages Router
import { useRouter } from "next/router" Pages Router
پوشه‌ی app، فایل‌های page.tsx و layout.tsx App Router
"use client" یا "use server" در ابتدای فایل App Router
import { useRouter } from "next/navigation" App Router

آیا می‌توان هر دو را همزمان داشت؟

بله. یک پروژه می‌تواند هم پوشه‌ی app و هم پوشه‌ی pages داشته باشد؛ این قابلیت برای مهاجرت تدریجی پروژه‌های قدیمی طراحی شده است. اما اگر هر دو Router یک مسیر مشابه را تعریف کنند، تداخل پیش می‌آید و Build خطا می‌دهد. در پروژه‌ی DevNotes فقط از پوشه‌ی app استفاده می‌کنیم و پوشه‌ی pages را نمی‌سازیم.

چرا در این دوره فقط App Router؟

  • App Router روش توصیه‌شده برای پروژه‌های جدید است و create-next-app آن را به‌صورت پیش‌فرض پیشنهاد می‌کند.
  • قابلیت‌های جدید Next.js 16، مثل Cache Components، use cache، Server Actions و Partial Prerendering، بر پایه‌ی App Router ارائه می‌شوند.
  • Pages Router همچنان پشتیبانی می‌شود و پروژه‌های موجود با آن کار می‌کنند. مهاجرت پروژه‌ی قدیمی به App Router خارج از محدوده‌ی این دوره است.

جمع‌بندی

  • Next.js دو Router دارد: Pages Router (پوشه‌ی pages، مدل قدیمی) و App Router (پوشه‌ی app، مدل جدید و توصیه‌شده).
  • App Router بر پایه‌ی Server Component، Layoutهای تودرتو، فایل‌های ویژه‌ی loading.tsx و error.tsx و دریافت داده در خود کامپوننت ساخته شده است.
  • در Pages Router دریافت داده با توابعی مثل getServerSideProps انجام می‌شود و Layoutها از _app.tsx می‌آیند.
  • با نشانه‌هایی مثل next/router در برابر next/navigation می‌توان کد هر Router را از هم تشخیص داد.
  • این دوره فقط با App Router و Next.js 16 پیش می‌رود.
  • در درس بعد، «ساخت صفحات با page.tsx»، ساخت صفحه در App Router را عملی تمرین می‌کنیم.