Route Groups و سازمان‌دهی مسیرها

تا اینجا هر پوشه در app یک بخش از آدرس را می‌ساخت. اما با بزرگ‌شدن پروژه، نیاز پیدا می‌کنیم صفحات را بر اساس نقش دسته‌بندی کنیم، نه بر اساس آدرس: مثلاً صفحات عمومی سایت، صفحات ورود و داشبورد مدیریت. در عین حال نمی‌خواهیم نام این دسته‌ها در آدرس‌ها ظاهر شود. Route Groups دقیقاً برای همین کار طراحی شده‌اند.

پیش‌نیاز

  • درس «Nested Layoutها»
  • درس «Root Layout، تنظیم lang="fa" و dir="rtl"»

Route Group چیست؟

اگر نام یک پوشه را داخل پرانتز بنویسید، مثل (public)، آن پوشه یک Route Group می‌شود. قاعده‌ی کار:

  • نام داخل پرانتز در آدرس نمی‌آید؛ فقط برای سازمان‌دهی فایل‌هاست.
  • هر گروه می‌تواند layout.tsx مخصوص خودش را داشته باشد و این Layout فقط روی مسیرهای داخل همان گروه اعمال می‌شود.
  • داخل گروه، پوشه‌ها و page.tsxها مثل همیشه مسیر می‌سازند.
مسیر فایل آدرس در مرورگر
app/(public)/about/page.tsx /about
app/(public)/page.tsx /
app/(dashboard)/dashboard/page.tsx /dashboard

توجه کنید که کلمه‌ی public و اولین dashboard داخل پرانتز در آدرس نیامده‌اند. در مثال سوم، dashboard بدون پرانتز یک پوشه‌ی معمولی است که بخش /dashboard را می‌سازد.

سه کاربرد اصلی Route Groups

  1. سازمان‌دهی: فایل‌های مربوط به یک ناحیه‌ی سایت کنار هم قرار می‌گیرند بدون اینکه آدرس‌ها تغییر کنند.
  2. Layout جداگانه برای بخشی از مسیرها: مثلاً صفحات عمومی هدر و فوتر دارند، ولی داشبورد منوی کناری.
  3. چند Root Layout: می‌توان هر گروه را یک Root Layout مستقل با html و body خودش داد. این روش کاربرد ویژه دارد و پایین‌تر کوتاه به آن می‌پردازیم.

عملی: بازسازی ساختار DevNotes با Route Groups

در درس «Nested Layoutها» هدر و فوتر را داخل Root Layout گذاشتیم تا ایده را ببینیم. اما داشبورد مدیریت قرار نیست همان هدر و فوتر عمومی را داشته باشد. پس ساختار را به این شکل اصلاح می‌کنیم:

  • Root Layout فقط تگ‌های html و body و تنظیمات سراسری را نگه می‌دارد.
  • هدر و فوتر به Layout گروه (public) منتقل می‌شوند.
  • داشبورد در گروه (dashboard) با Layout خودش ساخته می‌شود.

فایل‌های متأثر: app/layout.tsx (ساده‌تر می‌شود)، و فایل‌های page.tsx صفحه‌ی اصلی، about و posts که به داخل (public) منتقل می‌شوند. آدرس‌های سایت تغییری نمی‌کنند.

گام ۱: ساخت پوشه‌ها و انتقال فایل‌ها

پوشه‌ی app/(public) را بسازید و این فایل‌ها و پوشه‌ها را به داخل آن منتقل کنید:

  • app/page.tsx ← app/(public)/page.tsx
  • app/about ← app/(public)/about
  • app/posts ← app/(public)/posts

فایل‌های app/layout.tsx، app/globals.css و app/favicon.ico در جای خود می‌مانند. در ویرایشگر VS Code می‌توانید فایل‌ها را با کشیدن و رها کردن جابه‌جا کنید.

گام ۲: ساده‌کردن Root Layout

tsx
// app/layout.tsx
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";

export const metadata: Metadata = {
  title: "DevNotes",
  description: "وب‌سایتی برای انتشار و مدیریت نوشته‌های فنی",
};

export default function RootLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  return (
    <html lang="fa" dir="rtl">
      <body>{children}</body>
    </html>
  );
}

گام ۳: Layout گروه عمومی

tsx
// app/(public)/layout.tsx
import type { ReactNode } from "react";

export default function PublicLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  return (
    <>
      <header>
        <strong>DevNotes</strong>
      </header>
      {children}
      <footer>
        <small>پروژه‌ی آموزشی دوره‌ی Next.js 16</small>
      </footer>
    </>
  );
}

گام ۴: Layout و صفحه‌ی گروه داشبورد

tsx
// app/(dashboard)/layout.tsx
import type { ReactNode } from "react";

export default function DashboardLayout({
  children,
}: Readonly<{ children: ReactNode }>) {
  return (
    <div>
      <aside>
        <strong>داشبورد DevNotes</strong>
        <nav>
          <p>نوشته‌های من</p>
          <p>تنظیمات</p>
        </nav>
      </aside>
      {children}
    </div>
  );
}
tsx
// app/(dashboard)/dashboard/page.tsx
export default function DashboardPage() {
  return (
    <main>
      <h1>داشبورد مدیریت</h1>
      <p>مدیریت نوشته‌ها در فصل‌های بعد به این بخش اضافه می‌شود.</p>
    </main>
  );
}

ساختار نهایی پس از این درس

text
app/
├── layout.tsx                  ← Root Layout (فقط html و body)
├── globals.css
├── favicon.ico
├── (public)/
│   ├── layout.tsx              ← هدر و فوتر عمومی
│   ├── page.tsx                ← /
│   ├── about/
│   │   └── page.tsx            ← /about
│   └── posts/
│       ├── layout.tsx          ← Layout بخش نوشته‌ها
│       ├── page.tsx            ← /posts
│       └── new/
│           └── page.tsx        ← /posts/new
└── (dashboard)/
    ├── layout.tsx              ← منوی کناری داشبورد
    └── dashboard/
        └── page.tsx            ← /dashboard

توضیح کد

  • app/layout.tsx اکنون فقط ساختار سندِ HTML را تعریف می‌کند. هر چیزی که فقط به بخشی از سایت مربوط است از آن خارج شده است.
  • <>...</> یک Fragment است؛ چون PublicLayout می‌خواهد هدر، children و فوتر را بدون تگ اضافی برگرداند.
  • هیچ‌کدام از دو Layoutی که در گروه‌ها ساختیم تگ html یا body ندارند؛ آن‌ها همچنان از Root Layout مشترک می‌آیند.
  • آدرس‌ها بدون تغییر مانده‌اند: /، /about، /posts و /posts/new مثل قبل کار می‌کنند و آدرس جدید /dashboard هم اضافه شده است.

رفتار مورد انتظار

آدرس Layoutها (از بیرون به درون) آنچه می‌بینید
/ Root ← (public) هدر، محتوای صفحه، فوتر
/posts Root ← (public) ← posts هدر، خط «بخش نوشته‌ها»، محتوا، فوتر
/dashboard Root ← (dashboard) منوی کناری و «داشبورد مدیریت» بدون هدر و فوتر عمومی

اگر فایل‌ها را جابه‌جا کرده‌اید و صفحه‌ای ۴۰۴ داد، یک‌بار سرور توسعه را با Ctrl + C متوقف و دوباره با npm run dev اجرا کنید.

قاعده‌ها و محدودیت‌ها

  • تداخل مسیر: دو گروه مختلف نمی‌توانند یک آدرس نهایی یکسان بسازند. مثلاً app/(public)/about/page.tsx و app/(dashboard)/about/page.tsx هر دو به /about می‌رسند و Next.js خطا می‌دهد.
  • گروه بدون Layout: داشتن layout.tsx در گروه الزامی نیست. اگر نیازی نیست، می‌توان گروه را فقط برای مرتب‌کردن فایل‌ها ساخت.
  • گروه‌ها قابل تودرتو شدن هستند و می‌توان داخل یک گروه، گروه دیگری ساخت، ولی بیش‌ازحد تودرتوکردن فهم پروژه را سخت می‌کند.
  • گروه جای کنترل دسترسی نیست. قرار دادن صفحه در (dashboard) آن را محافظت نمی‌کند و /dashboard فعلاً برای همه باز است. محافظت واقعی را در فصل «Authentication و Security» و «Proxy و کنترل دسترسی» می‌سازیم.
  • جایگاه صفحه‌ی ایجاد نوشته: /posts/new فعلاً در بخش عمومی مانده است. محل نهایی آن را در درس «ساخت اسکلت اولیه‌ی DevNotes» و فصل‌های بعد، همراه با احراز هویت، دوباره بررسی می‌کنیم.

چند Root Layout در یک پروژه

می‌توان فایل app/layout.tsx را نداشت و در عوض در هر گروه یک Root Layout کامل با html و body ساخت. این کار وقتی مفید است که بخش‌های سایت اساساً سند HTML متفاوتی دارند، مثلاً زبان یا جهت متفاوت. دو نکته‌ی مهم:

  • اگر app/layout.tsx نداشته باشید، صفحه‌ی اصلی (/) باید داخل یکی از گروه‌ها تعریف شود.
  • ناوبری بین دو Root Layout متفاوت، بارگذاری کامل صفحه (Full Page Load) انجام می‌دهد، نه ناوبری سریع سمت کلاینت.

DevNotes فقط یک زبان و یک جهت دارد، پس فقط یک Root Layout نگه می‌داریم.

جمع‌بندی

  • Route Group پوشه‌ای با نام داخل پرانتز است که در آدرس ظاهر نمی‌شود و برای سازمان‌دهی مسیرها و جداکردن Layoutها به کار می‌رود.
  • هر گروه می‌تواند layout.tsx خودش را داشته باشد که فقط روی مسیرهای داخل همان گروه اعمال می‌شود.
  • دو گروه نباید آدرس نهایی یکسان بسازند.
  • قرار دادن صفحه در یک گروه آن را امن نمی‌کند؛ کنترل دسترسی جداگانه لازم است.
  • در DevNotes اکنون گروه‌های (public) و (dashboard) داریم و Root Layout فقط ساختار HTML را نگه می‌دارد.
  • در درس بعد، «پوشه‌های خصوصی و ساختاردهی فایل‌ها»، یاد می‌گیریم فایل‌های کمکی را کنار صفحات بگذاریم بدون اینکه مسیر عمومی شوند.