Nested Layoutها
در درس قبل Root Layout را ساختیم که برای تمام صفحات سایت اعمال میشود. اما بخشهای مختلف یک سایت اغلب ساختار مخصوص خودشان را دارند: مثلاً بخش نوشتهها یک سربرگ و چیدمان مشترک میخواهد و داشبورد مدیریت یک منوی کناری. Next.js برای این نیاز Layoutهای تودرتو (Nested Layouts) را فراهم کرده است. در این درس یاد میگیریم چطور برای هر بخش از مسیرها Layout جداگانه بسازیم و این Layoutها چگونه درون هم قرار میگیرند.
پیشنیاز
- درس «Root Layout، تنظیم lang="fa" و dir="rtl"»
- درس «ساخت صفحات با page.tsx»
ایدهی اصلی
هر پوشهی داخل app میتواند فایل layout.tsx خودش را داشته باشد. چنین Layoutی برای همان پوشه و تمام مسیرهای زیرمجموعهی آن اعمال میشود. وقتی چند Layout روی یک مسیر اثر بگذارند، از بیرونیترین (Root Layout) تا درونیترین، بهترتیب درون هم قرار میگیرند و هر Layout، بخش درونیتر را از طریق children میگیرد.
میتوان آن را مثل عروسکهای روسی دید: Root Layout بیرونیترین عروسک است، Layout بخش نوشتهها داخل آن قرار میگیرد و صفحهی نهایی در عمیقترین نقطه نشسته است.
ساختار پیشنهادی برای DevNotes
app/
├── layout.tsx ← Root Layout (کل سایت)
├── page.tsx ← /
├── about/
│ └── page.tsx ← /about
└── posts/
├── layout.tsx ← Layout مخصوص بخش نوشتهها
├── page.tsx ← /posts
└── new/
└── page.tsx ← /posts/new
با این ساختار، مسیرهای /posts و /posts/new هر دو درون app/posts/layout.tsx رندر میشوند، ولی / و /about از آن بیخبرند.
| آدرس | Layoutهای اعمالشده (از بیرون به درون) |
|---|---|
/ |
Root Layout ← صفحه |
/about |
Root Layout ← صفحه |
/posts |
Root Layout ← Layout نوشتهها ← صفحه |
/posts/new |
Root Layout ← Layout نوشتهها ← صفحه |
عملی: اضافهکردن هدر و فوتر به Root Layout
ابتدا بخشهای مشترک کل سایت را در Root Layout میگذاریم. فایل app/layout.tsx را به این شکل بهروزرسانی کنید (تغییر فقط داخل body است):
// 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>
<header>
<strong>DevNotes</strong>
</header>
{children}
<footer>
<small>پروژهی آموزشی دورهی Next.js 16</small>
</footer>
</body>
</html>
);
}هدر و فوتر در تمام صفحات سایت دیده میشوند. لینکهای ناوبری را بعداً، در درس «لینکسازی با Link»، به هدر اضافه میکنیم.
عملی: ساخت Layout بخش نوشتهها
حالا فایل app/posts/layout.tsx را بسازید:
// app/posts/layout.tsx
import type { ReactNode } from "react";
export default function PostsLayout({
children,
}: Readonly<{ children: ReactNode }>) {
return (
<section>
<p>
<strong>بخش نوشتهها</strong> | مقالههای فنی DevNotes
</p>
{children}
</section>
);
}عملی: صفحهی ایجاد نوشته
برای اینکه ببینیم Layout روی مسیرهای تودرتو هم اعمال میشود، یک صفحهی ساده در /posts/new میسازیم. فرم واقعی را در فصل «فرمها و Server Actions» اضافه میکنیم:
// app/posts/new/page.tsx
export default function NewPostPage() {
return (
<main>
<h1>نوشتهی جدید</h1>
<p>فرم ایجاد نوشته بهزودی در اینجا قرار میگیرد.</p>
</main>
);
}
صفحهی app/posts/page.tsx را از درس «ساخت صفحات با page.tsx» تغییر نمیدهیم.
توضیح کد
- هر Layout یک کامپوننت با پراپ
childrenاست و باید آن را در جای مناسب رندر کند. اگرchildrenرا رندر نکنید، صفحه نمایش داده نمیشود. - Layout بخش نوشتهها تگهای
htmlوbodyندارد؛ آنها فقط در Root Layout هستند. - نام تابع (
PostsLayout) دلخواه است، اما بهصورت قرارداد باLayoutتمام میشود. - این Layoutها بدون
"use client"هستند، پس Server Componentاند و روی سرور اجرا میشوند.
خروجی و رفتار مورد انتظار
| آدرس | آنچه روی صفحه میبینید |
|---|---|
/about |
هدر ← محتوای صفحه ← فوتر |
/posts |
هدر ← خط «بخش نوشتهها» ← محتوای صفحه ← فوتر |
/posts/new |
هدر ← خط «بخش نوشتهها» ← «نوشتهی جدید» ← فوتر |
ساختار HTML نهایی برای /posts تقریباً به این شکل درمیآید:
<body>
<header>...</header>
<section> <!-- Layout نوشتهها -->
<p>...</p>
<main>...</main> <!-- محتوای page.tsx -->
</section>
<footer>...</footer>
</body>رفتار Layout هنگام جابهجایی بین صفحات
یکی از مهمترین مزیتهای Layout این است که هنگام ناوبری، فقط بخشی که تغییر کرده دوباره رندر میشود:
- اگر از
/postsبه/posts/newبروید، Layout نوشتهها و Root Layout باقی میمانند و فقط صفحه عوض میشود. - اگر از
/postsبه/aboutبروید، Layout نوشتهها حذف میشود، ولی Root Layout همچنان باقی است. - چون Layout بین ناوبریها دوباره ساخته نمیشود، State کامپوننتهای کلاینتی داخل آن حفظ میشود. این نکته وقتی به Client Component رسیدیم کاربرد پیدا میکند (فصل «Server Components و Client Components»).
این رفتار با ایدهی «رندر جزئی» (Partial Rendering) شناخته میشود و دلیل سریعبودن ناوبری در App Router است.
چند نکتهی مهم
-
ارتباط Layout و صفحه از طریق props: Layout نمیتواند داده را از طریق props مستقیماً به
childrenبدهد؛childrenصرفاً یک جایگاه رندر است. برای اشتراک داده بین Layout و صفحات از دریافت داده در هرکدام، Context (در Client Componentها) یا توابع مشترک دسترسی به داده استفاده میکنیم. این موضوع را در فصل «Rendering و Data Fetching» میبینیم. -
پارامترهای مسیر پویا: Layout در مسیرهای پویا
paramsرا بهصورت یک Promise دریافت میکند، دقیقاً مثل صفحات. جزئیات را در درس «مسیرهای پویا، params ناهمگام و مسیرهای تودرتو» میبینیم. -
Layout فقط روی ریشهی زیرمسیرها اثر میگذارد: Layout در
app/postsروی/aboutاثری ندارد. اگر بخواهید یک Layout روی چند مسیرِ همسطح اعمال شود ولی در آدرس ظاهر نشود، از Route Groups استفاده میکنیم؛ موضوع درس «Route Groups و سازماندهی مسیرها». - Layout با Metadata: هر Layout هم میتواند Metadata خودش را تعریف کند و Metadata درونیتر مقدار بیرونیتر را بازنویسی میکند. جزئیات در درس «Metadata ثابت و پویا» است.
ساختار پروژه پس از این درس
app/
├── layout.tsx ← هدر و فوتر کل سایت
├── page.tsx ← /
├── globals.css
├── about/
│ └── page.tsx ← /about
└── posts/
├── layout.tsx ← Layout بخش نوشتهها
├── page.tsx ← /posts
└── new/
└── page.tsx ← /posts/newجمعبندی
- هر پوشهی داخل
appمیتواند فایلlayout.tsxداشته باشد که برای آن پوشه و تمام مسیرهای زیرمجموعهاش اعمال میشود. - Layoutها بهترتیب از Root Layout تا درونیترین Layout درون هم قرار میگیرند و هر کدام بخش درونی را با
childrenرندر میکند. - فقط Root Layout تگهای
htmlوbodyدارد. - هنگام جابهجایی بین صفحاتی که Layout مشترک دارند، Layout دوباره ساخته نمیشود و فقط بخش تغییرکرده بهروز میشود.
- در DevNotes اکنون Root Layout هدر و فوتر دارد و بخش
/postsیک Layout اختصاصی دارد. - در درس بعد، «Route Groups و سازماندهی مسیرها»، یاد میگیریم چطور مسیرها را بدون تغییر در آدرسها دستهبندی کنیم و به هر دسته Layout جدا بدهیم.
