پوشههای خصوصی و ساختاردهی فایلها
در درسهای قبل دیدیم که فقط پوشههایی که فایل page.tsx داشته باشند مسیر قابل مشاهده میسازند. این یعنی میتوانیم فایلهای دیگری، مثل کامپوننتها و توابع کمکی، کنار صفحات بگذاریم بدون اینکه ناخواسته آدرس عمومی بسازند. در این درس سه ابزار برای مرتب نگهداشتن پروژه را میبینیم: ایدهی Colocation، پوشههای خصوصی (Private Folders) و استراتژیهای ساختاردهی فایلها. سپس ساختار DevNotes را بر اساس آنها مرتب میکنیم.
پیشنیاز
- درس «Route Groups و سازماندهی مسیرها»
- درس «ساخت صفحات با page.tsx»
Colocation: فایلها را نزدیک جایی بگذارید که استفاده میشوند
در App Router هیچ فایلی فقط بهخاطر اینکه داخل app است مسیر عمومی نمیشود. فقط فایلهای با نامهای قراردادی مثل page.tsx و route.ts محتوایی را از آن مسیر عمومی میکنند. بنابراین میتوان فایلهای وابسته به یک صفحه را کنار همان صفحه نگه داشت:
app/(public)/posts/
├── page.tsx ← /posts (مسیر عمومی)
├── PostsIntro.tsx ← مسیر عمومی نمیسازد
└── helpers.ts ← مسیر عمومی نمیسازداین قابلیت «Colocation» نام دارد و باعث میشود وقتی صفحهای را تغییر میدهید، همهی فایلهای مرتبط جلوی چشم باشند.
پوشههای خصوصی (Private Folders)
اگر فایلهای کمکی همسطح با پوشههای مسیر باشند، تشخیص اینکه کدام پوشه «مسیر» و کدام «کمکی» است سخت میشود. برای حل این مشکل میتوان نام پوشه را با زیرخط شروع کرد: _components. چنین پوشهای:
- بهطور کامل از مسیریابی کنار گذاشته میشود؛ حتی اگر داخلش
page.tsxبگذارید، آدرسی ساخته نمیشود. - برای هر کسی که پروژه را میخواند نشان میدهد که این پوشه «داخلی» است.
- فایلهای داخلش مثل فایلهای معمولی import میشوند.
| مسیر فایل | آیا آدرس میسازد؟ |
|---|---|
app/posts/page.tsx |
بله، /posts |
app/posts/_components/PostsIntro.tsx |
خیر |
app/posts/_components/page.tsx |
خیر، چون کل پوشهی _components از مسیریابی خارج است |
اگر واقعاً نیاز دارید یک بخش از آدرس با زیرخط شروع شود، نام پوشه را با %5F (کد URL زیرخط) بنویسید، مثلاً %5Fadmin برای آدرس /_admin. این مورد در DevNotes پیش نمیآید.
تفاوت پوشهی خصوصی و Route Group
| ویژگی | Route Group مثل (public) |
پوشهی خصوصی مثل _components |
|---|---|---|
| نقش | گروهبندی مسیرها و Layoutها | نگهداری فایلهای غیرمسیری |
| مسیر داخلش ساخته میشود؟ | بله، فقط نام گروه از آدرس حذف میشود | خیر، هیچ مسیری ساخته نمیشود |
میتواند layout.tsx داشته باشد؟ |
بله | معنی ندارد |
استراتژیهای ساختاردهی پروژه
Next.js ساختار خاصی را اجبار نمیکند. سه الگوی رایج:
| الگو | شکل کار | مناسب برای |
|---|---|---|
۱. همهچیز خارج از app |
app فقط برای مسیریابی است؛ کامپوننتها و منطق در پوشههای ریشه مثل components و lib هستند |
پروژههای کوچک تا متوسط با کدهای مشترک زیاد |
۲. همهچیز داخل app |
فایلهای مشترک و غیرمسیری در پوشههای خصوصی داخل app نگه داشته میشوند |
کسانی که میخواهند همهچیز یکجا باشد |
| ۳. تقسیم بر اساس ویژگی یا مسیر | کدهای مشترک در بیرون یا ریشهی app و کدهای اختصاصی هر بخش کنار همان بخش |
پروژههای بزرگتر با چند بخش مستقل |
الگوی درست یکی نیست؛ مهم این است که یک قاعده انتخاب کنید و در کل پروژه رعایتش کنید. در DevNotes الگوی سوم را به شکل ساده به کار میبریم:
- کدهای مشترک بین چند صفحه (مثل هدر و فوتر) در پوشههای ریشهی پروژه:
componentsو بعداًlib. - کدهایی که فقط یک بخش استفاده میکند در پوشهی خصوصی
_componentsکنار همان بخش.
چون پروژه را بدون پوشهی src ساختهایم، پوشههای components و lib مستقیماً در کنار app قرار میگیرند. در فصلهای بعد، بخصوص درس «معماری و سازماندهی کد در پروژهی واقعی»، دوباره سراغ ساختار پروژه میرویم.
عملی: مرتبسازی ساختار DevNotes
در درس قبل هدر و فوتر را مستقیماً داخل app/(public)/layout.tsx نوشتیم. حالا آنها را به کامپوننتهای مشترک تبدیل میکنیم و برای صفحهی نوشتهها یک کامپوننت اختصاصی در پوشهی خصوصی میسازیم.
فایلهای متأثر: app/(public)/layout.tsx (سادهتر میشود)، app/(public)/posts/page.tsx (یک کامپوننت را استفاده میکند) و سه فایل جدید. آدرسهای سایت تغییر نمیکنند.
گام ۱: کامپوننتهای مشترک
پوشهی components را در ریشهی پروژه (همسطح با app) بسازید:
// components/SiteHeader.tsx
export default function SiteHeader() {
return (
<header>
<strong>DevNotes</strong>
</header>
);
}// components/SiteFooter.tsx
export default function SiteFooter() {
return (
<footer>
<small>پروژهی آموزشی دورهی Next.js 16</small>
</footer>
);
}گام ۲: استفاده در Layout گروه عمومی
// app/(public)/layout.tsx
import type { ReactNode } from "react";
import SiteHeader from "@/components/SiteHeader";
import SiteFooter from "@/components/SiteFooter";
export default function PublicLayout({
children,
}: Readonly<{ children: ReactNode }>) {
return (
<>
<SiteHeader />
{children}
<SiteFooter />
</>
);
}گام ۳: کامپوننت اختصاصی در پوشهی خصوصی
پوشهی _components را داخل app/(public)/posts بسازید:
// app/(public)/posts/_components/PostsIntro.tsx
export default function PostsIntro() {
return (
<p>
در این بخش نوشتههای فنی DevNotes فهرست میشوند. فهرست واقعی
بعد از اتصال به داده اضافه میشود.
</p>
);
}گام ۴: استفاده در صفحهی نوشتهها
// app/(public)/posts/page.tsx
import PostsIntro from "./_components/PostsIntro";
export default function PostsPage() {
return (
<main>
<h1>نوشتهها</h1>
<PostsIntro />
</main>
);
}توضیح کد
@/components/SiteHeader: alias مسیر@/*از درس «ساختار فایلها و پوشهها» است و به ریشهی پروژه اشاره میکند. برای کدهای مشترک از آن استفاده میکنیم تا مسیرهای نسبی طولانی (مثل../../../components) نداشته باشیم../_components/PostsIntro: چون فایل کنار صفحه است، مسیر نسبی کوتاه و گویا است؛ همین نزدیکی به معنی «فقط این بخش از آن استفاده میکند».- هیچکدام از این کامپوننتها
"use client"ندارند، پس Server Component هستند. اگر در آینده یکی از آنها به State یا رویداد نیاز پیدا کند، فقط همان را Client Component میکنیم. - خروجی HTML تغییری نکرده است؛ فقط کد مرتبتر شده است. این کار «بازسازی» (Refactoring) نام دارد.
ساختار پروژه پس از این درس
devnotes/
├── app/
│ ├── layout.tsx
│ ├── globals.css
│ ├── (public)/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── about/
│ │ │ └── page.tsx
│ │ └── posts/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── _components/
│ │ │ └── PostsIntro.tsx
│ │ └── new/
│ │ └── page.tsx
│ └── (dashboard)/
│ ├── layout.tsx
│ └── dashboard/
│ └── page.tsx
└── components/
├── SiteHeader.tsx
└── SiteFooter.tsxخروجی و رفتار مورد انتظار
- صفحات
/،/about،/posts،/posts/newو/dashboardدقیقاً مثل قبل کار میکنند. - در
/postsعلاوه بر عنوان، متن توضیحی کامپوننتPostsIntroهم دیده میشود. - آدرس
/posts/_componentsصفحهی ۴۰۴ میدهد؛ چون پوشهی خصوصی هیچ مسیری نمیسازد.
نکتههای مهم
- یک قاعده انتخاب کنید و به آن وفادار بمانید. مهمتر از «بهترین ساختار» این است که ساختار در کل پروژه یکدست باشد.
-
زیرخط فقط برای مسیریابی است. اگر پوشهی کمکیتان را با
_شروع نکنید هم مسیر ساخته نمیشود (چونpage.tsxندارد)، اما با پیشوند زیرخط خوانندگان کد فوراً تشخیص میدهند که پوشهی داخلی است. همچنین از تداخل ناخواسته با نامهای قراردادی آیندهی Next.js جلوگیری میکند. -
فایلهای ویژه نامهای قراردادی ثابت دارند.
page،layout،loading،error،not-foundوrouteنامهایی هستند که Next.js میشناسد. نام کامپوننتهای خودتان نباید با اینها اشتباه گرفته شود. فهرست کامل را در درس بعد، «فایلهای ویژهی App Router»، میبینیم. - کد حساس سرور را در مسیر درست نگه دارید. قرار دادن یک فایل کنار صفحه آن را برای کاربر قابل دسترسی نمیکند، اما اگر یک Client Component آن را import کند، کدش به مرورگر میرود. محافظت از کد سرور را در درس «server-only، Context و اشتباهات رایج» میبینیم.
جمعبندی
- در App Router فقط فایلهای قراردادی مثل
page.tsxوroute.tsمحتوای عمومی میسازند و بقیهی فایلها را میتوان کنار صفحات گذاشت (Colocation). - پوشهای که نامش با زیرخط شروع شود (
_components) یک پوشهی خصوصی است و کاملاً از مسیریابی خارج میشود. - Route Group برای دستهبندی مسیرها و Layoutها است و پوشهی خصوصی برای نگهداری فایلهای غیرمسیری؛ این دو یکی نیستند.
- سه الگوی رایج ساختاردهی وجود دارد و ما در DevNotes کدهای مشترک را در
components(و بعداًlib) و کدهای اختصاصی را در_componentsکنار صفحه نگه میداریم. - در این درس هدر و فوتر را به
componentsمنتقل کردیم وPostsIntroرا در پوشهی خصوصی ساختیم. - در درس بعد، «فایلهای ویژهی App Router»، با مهمترین فایلهای قراردادی مثل
loading.tsx،error.tsxوnot-found.tsxآشنا میشویم.
