رابطهای کاربری مقیاسپذیر و پویا در Next.js را فعال کنید. راهنمای جامع ما گروههای مسیر (Route Groups) برای سازماندهی و مسیرهای موازی (Parallel Routes) برای داشبوردهای پیچیده را پوشش میدهد. همین حالا سطح خود را بالا ببرید!
تسلط بر App Router در Next.js: نگاهی عمیق به معماری گروههای مسیر و مسیرهای موازی
انتشار App Router در Next.js یک تغییر پارادایم در نحوه ساخت اپلیکیشنهای وب توسط توسعهدهندگان با این فریمورک محبوب React بود. App Router با فاصله گرفتن از قراردادهای مبتنی بر فایل در Pages Router، یک مدل قدرتمندتر، انعطافپذیرتر و سرور-محور را معرفی کرد. این تحول به ما این امکان را میدهد که رابطهای کاربری بسیار پیچیده و با کارایی بالا را با کنترل و سازماندهی بیشتری ایجاد کنیم. از جمله تحولآفرینترین ویژگیهای معرفی شده میتوان به گروههای مسیر (Route Groups) و مسیرهای موازی (Parallel Routes) اشاره کرد.
برای توسعهدهندگانی که قصد ساخت اپلیکیشنهای در سطح سازمانی (enterprise-grade) را دارند، تسلط بر این دو مفهوم فقط مفید نیست—بلکه ضروری است. آنها چالشهای رایج معماری مربوط به مدیریت لایوت، سازماندهی مسیرها و ایجاد رابطهای پویا و چند پنلی مانند داشبوردها را حل میکنند. این راهنما یک کاوش جامع در مورد گروههای مسیر و مسیرهای موازی، از مفاهیم بنیادی گرفته تا استراتژیهای پیادهسازی پیشرفته و بهترین شیوهها برای مخاطبان جهانی توسعهدهنده ارائه میدهد.
درک App Router در Next.js: یک یادآوری سریع
قبل از اینکه به جزئیات بپردازیم، بیایید به طور خلاصه اصول اصلی App Router را مرور کنیم. معماری آن بر اساس یک سیستم مبتنی بر دایرکتوری ساخته شده است که در آن پوشهها بخشهای URL را تعریف میکنند. فایلهای ویژه در این پوشهها، رابط کاربری و رفتار آن بخش را تعریف میکنند:
page.js
: کامپوننت اصلی رابط کاربری برای یک مسیر است که آن را به صورت عمومی قابل دسترس میکند.layout.js
: یک کامپوننت رابط کاربری است که لایوتها یا صفحات فرزند را در بر میگیرد. این برای به اشتراکگذاری UI در چندین مسیر، مانند هدرها و فوترها، حیاتی است.loading.js
: یک UI اختیاری برای نمایش در حین بارگذاری محتوای صفحه است که بر پایه React Suspense ساخته شده است.error.js
: یک UI اختیاری برای نمایش در صورت بروز خطا است که مرزهای خطای قوی ایجاد میکند.
این ساختار، همراه با استفاده پیشفرض از کامپوننتهای سرور React (RSCs)، یک رویکرد سرور-محور را تشویق میکند که میتواند به طور قابل توجهی عملکرد و الگوهای واکشی داده را بهبود بخشد. گروههای مسیر و مسیرهای موازی قراردادهای پیشرفتهای هستند که بر این پایه ساخته شدهاند.
رمزگشایی از گروههای مسیر: سازماندهی پروژه برای سلامت روان و مقیاسپذیری
با رشد یک اپلیکیشن، تعداد مسیرها میتواند غیرقابل کنترل شود. ممکن است مجموعهای از صفحات برای بازاریابی، مجموعهای دیگر برای احراز هویت کاربر و مجموعهای سوم برای داشبورد اصلی اپلیکیشن داشته باشید. از نظر منطقی، اینها بخشهای جداگانهای هستند، اما چگونه آنها را در سیستم فایل خود سازماندهی میکنید بدون اینکه URLهای خود را شلوغ کنید؟ این دقیقاً مشکلی است که گروههای مسیر حل میکنند.
گروههای مسیر چه هستند؟
یک گروه مسیر مکانیزمی برای سازماندهی فایلها و بخشهای مسیر شما به گروههای منطقی است بدون اینکه بر ساختار URL تأثیر بگذارد. شما یک گروه مسیر را با قرار دادن نام یک پوشه در داخل پرانتز ایجاد میکنید، به عنوان مثال، (marketing)
یا (app)
.
نام پوشه داخل پرانتز صرفاً برای اهداف سازمانی است. Next.js هنگام تعیین مسیر URL آن را کاملاً نادیده میگیرد. به عنوان مثال، فایل موجود در app/(marketing)/about/page.js
در URL /about
ارائه میشود، نه /(marketing)/about
.
موارد استفاده کلیدی و مزایای گروههای مسیر
در حالی که سازماندهی ساده یک مزیت است، قدرت واقعی گروههای مسیر در توانایی آنها برای تقسیم اپلیکیشن شما به بخشهایی با لایوتهای مشترک و متمایز نهفته است.
۱. ایجاد لایوتهای مختلف برای بخشهای مسیر
این رایجترین و قدرتمندترین مورد استفاده است. یک اپلیکیشن وب با دو بخش اصلی را تصور کنید:
- یک سایت بازاریابی عمومی (صفحه اصلی، درباره ما، قیمتگذاری) با یک هدر و فوتر سراسری.
- یک داشبورد کاربری خصوصی و احراز هویت شده (داشبورد، تنظیمات، پروفایل) با یک نوار کناری، ناوبری مخصوص کاربر و ساختار کلی متفاوت.
بدون گروههای مسیر، اعمال لایوتهای ریشه متفاوت به این بخشها پیچیده خواهد بود. با گروههای مسیر، این کار به طرز باورنکردنیای شهودی است. شما میتوانید یک فایل layout.js
منحصر به فرد در داخل هر گروه ایجاد کنید.
در اینجا یک ساختار فایل معمولی برای این سناریو آمده است:
app/
├── (marketing)/
│ ├── layout.js // لایوت عمومی با هدر/فوتر بازاریابی
│ ├── page.js // در '/' رندر میشود
│ └── about/
│ └── page.js // در '/about' رندر میشود
├── (app)/
│ ├── layout.js // لایوت داشبورد با نوار کناری
│ ├── dashboard/
│ │ └── page.js // در '/dashboard' رندر میشود
│ └── settings/
│ └── page.js // در '/settings' رندر میشود
└── layout.js // لایوت ریشه (مثلاً برای تگهای <html> و <body>)
در این معماری:
- هر مسیری در داخل گروه
(marketing)
توسط(marketing)/layout.js
در بر گرفته میشود. - هر مسیری در داخل گروه
(app)
توسط(app)/layout.js
در بر گرفته میشود. - هر دو گروه از لایوت ریشه
app/layout.js
استفاده میکنند که برای تعریف ساختار HTML سراسری عالی است.
۲. خارج کردن یک بخش از یک لایوت مشترک
گاهی اوقات، یک صفحه یا بخش خاص نیاز دارد تا به طور کامل از لایوت والد خود جدا شود. یک مثال رایج، فرآیند پرداخت یا یک صفحه فرود ویژه است که نباید ناوبری اصلی سایت را داشته باشد. شما میتوانید این کار را با قرار دادن مسیر در یک گروهی که از لایوت سطح بالاتر استفاده نمیکند، انجام دهید. اگرچه این پیچیده به نظر میرسد، اما به سادگی به معنای دادن یک layout.js
سطح بالای خودش به یک گروه مسیر است که `children` را از لایوت ریشه رندر نمیکند.
مثال عملی: ساخت یک اپلیکیشن چند-لایوتی
بیایید یک نسخه حداقلی از ساختار بازاریابی/اپلیکیشن که در بالا توضیح داده شد را بسازیم.
۱. لایوت ریشه (app/layout.js
)
این لایوت حداقل است و برای هر صفحهای اعمال میشود. این لایوت ساختار ضروری HTML را تعریف میکند.
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="fa">
<body>{children}</body>
</html>
);
}
۲. لایوت بازاریابی (app/(marketing)/layout.js
)
این لایوت شامل یک هدر و فوتر عمومی است.
// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
return (
<div>
<header>هدر بازاریابی</header>
<main>{children}</main>
<footer>فوتر بازاریابی</footer>
</div>
);
}
۳. لایوت داشبورد اپلیکیشن (app/(app)/layout.js
)
این لایوت ساختار متفاوتی دارد و دارای یک نوار کناری برای کاربران احراز هویت شده است.
// app/(app)/layout.js
export default function AppLayout({ children }) {
return (
<div style={{ display: 'flex' }}>
<aside style={{ width: '200px', borderRight: '1px solid #ccc' }}>
نوار کناری داشبورد
</aside>
<main style={{ flex: 1, padding: '20px' }}>{children}</main>
</div>
);
}
با این ساختار، پیمایش به /about
صفحه را با `MarketingLayout` رندر میکند، در حالی که پیمایش به /dashboard
آن را با `AppLayout` رندر میکند. URL تمیز و معنایی باقی میماند، در حالی که ساختار فایل پروژه ما کاملاً سازماندهی شده و مقیاسپذیر است.
باز کردن قفل رابطهای کاربری پویا با مسیرهای موازی
در حالی که گروههای مسیر به سازماندهی بخشهای متمایز یک اپلیکیشن کمک میکنند، مسیرهای موازی چالش متفاوتی را حل میکنند: نمایش چندین نمای صفحه مستقل در یک لایوت واحد. این یک نیاز رایج برای داشبوردهای پیچیده، فیدهای رسانههای اجتماعی یا هر رابط کاربری است که در آن پنلهای مختلف باید به طور همزمان رندر و مدیریت شوند.
مسیرهای موازی چه هستند؟
مسیرهای موازی به شما امکان میدهند تا یک یا چند صفحه را به طور همزمان در همان لایوت رندر کنید. این مسیرها با استفاده از یک قرارداد پوشهبندی ویژه به نام اسلاتها (slots) تعریف میشوند. اسلاتها با استفاده از سینتکس @folderName
ایجاد میشوند. آنها بخشی از ساختار URL نیستند؛ در عوض، به طور خودکار به عنوان props به نزدیکترین فایل `layout.js` والد مشترک ارسال میشوند.
به عنوان مثال، اگر لایوتی دارید که باید یک فید فعالیت تیم و یک نمودار تحلیلی را کنار هم نمایش دهد، میتوانید دو اسلات تعریف کنید: `@team` و `@analytics`.
ایده اصلی: اسلاتها
اسلاتها را به عنوان جایگاههای نامگذاری شده در لایوت خود در نظر بگیرید. فایل لایوت به صراحت این اسلاتها را به عنوان props میپذیرد و تصمیم میگیرد که آنها را کجا رندر کند.
این کامپوننت لایوت را در نظر بگیرید:
// لایوتی که دو اسلات را میپذیرد: 'team' و 'analytics'
export default function DashboardLayout({ children, team, analytics }) {
return (
<div>
{children}
<div style={{ display: 'flex' }}>
{team}
{analytics}
</div>
</div>
);
}
در اینجا، `children`، `team` و `analytics` همگی اسلات هستند. `children` یک اسلات ضمنی است که با فایل استاندارد `page.js` در دایرکتوری مطابقت دارد. `team` و `analytics` اسلاتهای صریح هستند که باید با پیشوند `@` در سیستم فایل ایجاد شوند.
ویژگیها و مزایای کلیدی
- مدیریت مسیر مستقل: هر مسیر موازی (اسلات) میتواند حالتهای loading و error خود را داشته باشد. این بدان معناست که پنل تحلیلی شما میتواند یک اسپینر بارگذاری را نشان دهد در حالی که فید تیم از قبل رندر شده است، که منجر به تجربه کاربری بسیار بهتری میشود.
- رندر شرطی: شما میتوانید به صورت برنامهریزی شده تصمیم بگیرید که کدام اسلاتها را بر اساس شرایط خاصی، مانند وضعیت احراز هویت کاربر یا مجوزها، رندر کنید.
- ناوبری فرعی: هر اسلات میتواند به طور مستقل و بدون تأثیر بر سایر اسلاتها پیمایش شود. این برای رابطهای تبدار یا داشبوردهایی که وضعیت یک پنل کاملاً از دیگری جدا است، عالی است.
یک سناریوی واقعی: ساخت یک داشبورد پیچیده
بیایید یک داشبورد در URL /dashboard
طراحی کنیم. این داشبورد یک بخش محتوای اصلی، یک پنل فعالیت تیم و یک پنل تحلیل عملکرد خواهد داشت.
ساختار فایل:
app/
└── dashboard/
├── @analytics/
│ ├── page.js // UI برای اسلات analytics
│ └── loading.js // UI بارگذاری مخصوص analytics
├── @team/
│ └── page.js // UI برای اسلات team
├── layout.js // لایوتی که اسلاتها را هماهنگ میکند
└── page.js // اسلات ضمنی 'children' (محتوای اصلی)
۱. لایوت داشبورد (app/dashboard/layout.js
)
این لایوت سه اسلات را دریافت و مرتب میکند.
// app/dashboard/layout.js
export default function DashboardLayout({ children, analytics, team }) {
const isLoggedIn = true; // با منطق احراز هویت واقعی جایگزین شود
return isLoggedIn ? (
<div>
<h1>داشبورد اصلی</h1>
{children}
<div style={{ marginTop: '20px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div style={{ border: '1px solid blue', padding: '10px' }}>
<h2>فعالیت تیم</h2>
{team}
</div>
<div style={{ border: '1px solid green', padding: '10px' }}>
<h2>تحلیل عملکرد</h2>
{analytics}
</div>
</div>
</div>
) : (
<div>لطفاً برای مشاهده داشبورد وارد شوید.</div>
);
}
۲. صفحات اسلات (مثلاً app/dashboard/@analytics/page.js
)
فایل `page.js` هر اسلات حاوی UI برای آن پنل خاص است.
// app/dashboard/@analytics/page.js
async function getAnalyticsData() {
// شبیهسازی یک درخواست شبکه
await new Promise(resolve => setTimeout(resolve, 3000));
return { views: '1.2M', revenue: '$50,000' };
}
export default async function AnalyticsPage() {
const data = await getAnalyticsData();
return (
<div>
<p>تعداد بازدیدها: {data.views}</p>
<p>درآمد: {data.revenue}</p>
</div>
);
}
// app/dashboard/@analytics/loading.js
export default function Loading() {
return <p>در حال بارگذاری دادههای تحلیلی...</p>;
}
با این تنظیمات، هنگامی که کاربر به /dashboard
پیمایش میکند، Next.js `DashboardLayout` را رندر میکند. لایوت محتوای رندر شده از dashboard/page.js
، dashboard/@team/page.js
و dashboard/@analytics/page.js
را به عنوان props دریافت کرده و آنها را بر اساس آن قرار میدهد. نکته مهم این است که پنل تحلیلی حالت loading.js
خود را به مدت ۳ ثانیه بدون مسدود کردن رندر بقیه داشبورد نشان میدهد.
مدیریت مسیرهای نامطابق با `default.js`
یک سوال حیاتی پیش میآید: چه اتفاقی میافتد اگر Next.js نتواند حالت فعال یک اسلات را برای URL فعلی بازیابی کند؟ به عنوان مثال، در هنگام بارگذاری اولیه یا بارگذاری مجدد صفحه، URL ممکن است /dashboard
باشد، که دستورالعمل خاصی برای نمایش محتوا در اسلاتهای @team
یا @analytics
ارائه نمیدهد. به طور پیشفرض، Next.js یک خطای 404 را رندر میکند.
برای جلوگیری از این امر، میتوانیم با ایجاد یک فایل default.js
در داخل مسیر موازی، یک UI جایگزین (fallback) ارائه دهیم.
مثال:
// app/dashboard/@analytics/default.js
export default function DefaultAnalyticsPage() {
return (
<div>
<p>هیچ داده تحلیلی انتخاب نشده است.</p>
</div>
);
}
اکنون، اگر اسلات تحلیلی نامطابق باشد، Next.js به جای یک صفحه 404، محتوای `default.js` را رندر میکند. این برای ایجاد یک تجربه کاربری روان، به خصوص در بارگذاری اولیه یک تنظیمات پیچیده مسیر موازی، ضروری است.
ترکیب گروههای مسیر و مسیرهای موازی برای معماریهای پیشرفته
قدرت واقعی App Router زمانی مشخص میشود که شما ویژگیهای آن را با هم ترکیب کنید. گروههای مسیر و مسیرهای موازی به زیبایی با هم کار میکنند تا معماریهای اپلیکیشن پیچیده و بسیار سازمانیافتهای ایجاد کنند.
مورد استفاده: یک نمایشگر محتوای چند-حالتی
پلتفرمی مانند یک گالری رسانه یا یک نمایشگر سند را تصور کنید که در آن کاربر میتواند یک آیتم را مشاهده کند اما همچنین یک پنجره مودال را برای دیدن جزئیات آن باز کند بدون اینکه زمینه صفحه پسزمینه را از دست بدهد. این اغلب «مسیر رهگیری شده» (Intercepting Route) نامیده میشود و یک الگوی قدرتمند است که بر روی مسیرهای موازی ساخته شده است.
بیایید یک گالری عکس ایجاد کنیم. وقتی روی یک عکس کلیک میکنید، در یک مودال باز میشود. اما اگر صفحه را رفرش کنید یا مستقیماً به URL عکس بروید، باید یک صفحه اختصاصی برای آن عکس نشان داده شود.
ساختار فایل:
app/
├── @modal/(..)(..)photos/[id]/page.js // مسیر رهگیری شده برای مودال
├── photos/
│ └── [id]/
│ └── page.js // صفحه اختصاصی عکس
├── layout.js // لایوت ریشه که اسلات @modal را دریافت میکند
└── page.js // صفحه اصلی گالری
توضیح:
- ما یک اسلات مسیر موازی به نام
@modal
ایجاد میکنیم. - مسیر عجیب و غریب
(..)(..)photos/[id]
از یک قرارداد به نام «بخشهای catch-all» برای مطابقت با مسیرphotos/[id]
از دو سطح بالاتر (از ریشه) استفاده میکند. - هنگامی که یک کاربر از صفحه اصلی گالری (
/
) به یک عکس میرود، Next.js این پیمایش را رهگیری کرده و به جای انجام یک پیمایش کامل صفحه، صفحه مودال را در داخل اسلات@modal
رندر میکند. - صفحه اصلی گالری در پراپ
children
لایوت قابل مشاهده باقی میماند. - اگر کاربر مستقیماً از
/photos/123
بازدید کند، رهگیری فعال نمیشود و صفحه اختصاصی درphotos/[id]/page.js
به طور معمول رندر میشود.
این الگو مسیرهای موازی (اسلات @modal
) را با قراردادهای مسیریابی پیشرفته ترکیب میکند تا یک تجربه کاربری یکپارچه ایجاد کند که پیادهسازی دستی آن بسیار پیچیده خواهد بود.
بهترین شیوهها و دامهای رایج
بهترین شیوهها برای گروههای مسیر
- از نامهای توصیفی استفاده کنید: نامهای معناداری مانند
(auth)
،(marketing)
یا(protected)
را انتخاب کنید تا ساختار پروژه شما خود-مستند باشد. - تا حد امکان ساختار را مسطح نگه دارید: از تودرتویی بیش از حد گروههای مسیر خودداری کنید. یک ساختار مسطحتر معمولاً برای درک و نگهداری آسانتر است.
- هدف آنها را به خاطر بسپارید: از آنها برای تقسیمبندی لایوت و سازماندهی استفاده کنید، نه برای ایجاد بخشهای URL.
بهترین شیوهها برای مسیرهای موازی
- همیشه یک `default.js` ارائه دهید: برای هر استفاده غیر trivial از مسیرهای موازی، یک فایل `default.js` برای مدیریت بارگذاریهای اولیه و حالتهای نامطابق به صورت روان، قرار دهید.
- از حالتهای بارگذاری granular استفاده کنید: یک فایل `loading.js` را در داخل دایرکتوری هر اسلات قرار دهید تا بازخورد فوری به کاربر ارائه دهید و از آبشارهای UI جلوگیری کنید.
- برای UI مستقل استفاده کنید: مسیرهای موازی زمانی میدرخشند که محتوای هر اسلات واقعاً مستقل باشد. اگر پنلها عمیقاً به هم مرتبط هستند، ممکن است ارسال props از طریق یک درخت کامپوننت واحد، راهحل سادهتری باشد.
دامهای رایج برای اجتناب
- فراموش کردن قراردادها: یک اشتباه رایج فراموش کردن پرانتز
()
برای گروههای مسیر یا علامت @ برای اسلاتهای مسیر موازی است. این باعث میشود که آنها به عنوان بخشهای URL معمولی در نظر گرفته شوند. - نبود `default.js`: شایعترین مشکل با مسیرهای موازی، مشاهده خطاهای غیرمنتظره 404 است زیرا یک فایل `default.js` جایگزین برای اسلاتهای نامطابق ارائه نشده است.
- درک نادرست `children`: در یک لایوت که از مسیرهای موازی استفاده میکند، به یاد داشته باشید که `children` فقط یکی از اسلاتها است که به طور ضمنی به `page.js` یا لایوت تودرتو در همان دایرکتوری نگاشت شده است.
نتیجهگیری: ساختن آینده اپلیکیشنهای وب
App Router در Next.js، با ویژگیهایی مانند گروههای مسیر و مسیرهای موازی، یک پایه محکم و مقیاسپذیر برای توسعه وب مدرن فراهم میکند. گروههای مسیر یک راهحل زیبا برای سازماندهی کد و اعمال لایوتهای متمایز بدون به خطر انداختن معناشناسی URL ارائه میدهند. مسیرهای موازی توانایی ساخت رابطهای پویا و چند پنلی با حالتهای مستقل را باز میکنند، چیزی که قبلاً فقط از طریق مدیریت حالت پیچیده سمت کلاینت قابل دستیابی بود.
با درک و ترکیب این الگوهای معماری قدرتمند، میتوانید از وبسایتهای ساده فراتر رفته و شروع به ساخت اپلیکیشنهای پیچیده، با کارایی بالا و قابل نگهداری کنید که پاسخگوی نیازهای کاربران امروزی باشد. منحنی یادگیری ممکن است تندتر از Pages Router کلاسیک باشد، اما بازده آن از نظر معماری اپلیکیشن و تجربه کاربری بسیار زیاد است. شروع به آزمایش با این مفاهیم در پروژه بعدی خود کنید و پتانسیل کامل Next.js را باز کنید.