آموزش Next.js به شما کمک میکند تا بهطور مؤثر از این فریمورک برای ساخت برنامههای وب مدرن استفاده کنید. یکی از ویژگیهای اساسی در توسعه برنامههای وب، مسیریابی (Routing) است. در این مقاله، به بررسی اصول مسیریابی (Routing Fundamentals) در Next.js خواهیم پرداخت. این آموزش به زبان ساده و قابلفهم نوشته شده است تا حتی مبتدیان بتوانند از آن بهرهمند شوند و با مفاهیم ابتدایی تا پیشرفته مسیریابی در Next.js آشنا شوند.
طرحبندیها و الگوها (Layouts and Templates)
در Next.js، میتوانید از طرحبندیها (Layouts) و الگوها (Templates) برای ایجاد صفحات با بخشهای مشترک مانند هدر و فوتر استفاده کنید. این ویژگی به شما این امکان را میدهد که این بخشها را یکبار تعریف کنید و در تمام صفحات استفاده نمایید.
چگونگی ایجاد طرحبندیها و صفحات
Next.js از مسیریابی مبتنی بر فایل سیستم (file-system based routing) استفاده میکند، به این معنا که شما میتوانید از پوشهها و فایلها برای تعریف مسیرها استفاده کنید. این صفحه شما را راهنمایی میکند که چگونه میتوانید طرحبندیها و صفحات را ایجاد کرده و بین آنها لینکدهی کنید.
ایجاد یک صفحه
یک صفحه UI است که در مسیر مشخصی رندر میشود. برای ایجاد یک صفحه، کافی است یک فایل صفحه در داخل دایرکتوری app اضافه کرده و یک کامپوننت React را بهطور پیشفرض صادر کنید. برای مثال، برای ایجاد صفحهای به نام index (/):
// app/page.tsx
export default function Page() {
return <h1>Hello Next.js!</h1>
}
در اینجا، صفحهای که ایجاد کردید به مسیر اصلی (/) مرتبط خواهد شد.
ایجاد یک طرحبندی
طرحبندی یک UI است که بین چندین صفحه به اشتراک گذاشته میشود. هنگام مسیریابی، طرحبندیها وضعیت خود را حفظ میکنند، همچنان تعاملی باقی میمانند و دوباره رندر نمیشوند.
شما میتوانید یک طرحبندی را با صادر کردن یک کامپوننت React از یک فایل طرحبندی (layout file) تعریف کنید. این کامپوننت باید یک children prop را بپذیرد که میتواند یک صفحه یا یک طرحبندی دیگر باشد.
برای مثال، برای ایجاد یک طرحبندی که صفحه index را بهعنوان فرزند میپذیرد، یک فایل طرحبندی داخل دایرکتوری app اضافه میکنید:
// app/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{/* UI طرحبندی */}
{/* محل نمایش children که میتواند یک صفحه یا طرحبندی داخلی باشد */}
<main>{children}</main>
</body>
</html>
)
}
طرحبندی بالا بهعنوان یک طرحبندی ریشه (root layout) شناخته میشود چرا که در ریشه دایرکتوری app تعریف شده است. طرحبندی ریشه ضروری است و باید شامل تگهای html و body باشد.
ایجاد یک مسیر تو در تو (Nested Route)
یک مسیر تو در تو مسیری است که از چندین بخش URL تشکیل شده است. برای مثال، مسیر /blog/[slug] از سه بخش تشکیل شده است:
/ (بخش ریشه)
blog (بخش)
[slug] (بخش برگ)
در Next.js:
پوشهها برای تعریف بخشهای مسیر که به بخشهای URL نگاشت میشوند، استفاده میشوند.
فایلها (مثل صفحه و طرحبندی) برای ایجاد UI که برای یک بخش نمایش داده میشود، استفاده میشوند.
برای ایجاد مسیرهای تو در تو، میتوانید پوشهها را داخل یکدیگر قرار دهید. برای مثال، برای افزودن مسیر /blog، یک پوشه به نام blog در دایرکتوری app ایجاد کنید. سپس برای دسترسی عمومی به /blog، یک فایل صفحه اضافه کنید:
// app/blog/page.tsx
import { getPosts } from '@/lib/posts'
import { Post } from '@/ui/post'
export default async function Page() {
const posts = await getPosts()
return (
<ul>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</ul>
)
}
برای ایجاد مسیرهایی تو در توی بیشتر، میتوانید پوشهها را ادامه دهید. برای مثال، برای ایجاد مسیری برای یک پست خاص در وبلاگ، یک پوشه جدید به نام [slug] داخل پوشه blog ایجاد کرده و یک فایل صفحه اضافه کنید:
// app/blog/[slug]/page.tsx
export default function Page() {
return <h1>Hello, Blog Post Page!</h1>
}
نکتهای که باید بدانید این است که در هنگام نامگذاری پوشهها، قرار دادن نام پوشهها در داخل براکتهای مربع (مثلاً [slug]) باعث ایجاد یک بخش خاص از مسیر داینامیک میشود که از دادهها چندین صفحه تولید میکند. این ویژگی برای پستهای وبلاگ، صفحات محصولات و غیره مفید است.
تو در تو کردن طرحبندیها (Nesting Layouts)
بهطور پیشفرض، طرحبندیها در سلسلهمراتب پوشهها بهطور تو در تو قرار دارند، به این معنا که طرحبندیهای فرزند از طریق children prop به طرحبندی والد متصل میشوند. شما میتوانید طرحبندیها را با اضافه کردن فایل طرحبندی داخل بخشهای خاص مسیر (پوشهها) تو در تو کنید.
برای مثال، برای ایجاد یک طرحبندی برای مسیر /blog، یک فایل طرحبندی جدید داخل پوشه blog اضافه کنید:
// app/blog/layout.tsx
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return <section>{children}</section>
}
اگر شما دو طرحبندی بالا را ترکیب کنید، طرحبندی ریشه (app/layout.js) طرحبندی وبلاگ (app/blog/layout.js) را دربرمیگیرد که این طرحبندی نیز پستهای وبلاگ (app/blog/page.js) و صفحه پست خاص وبلاگ (app/blog/[slug]/page.js) را دربرمیگیرد.
لینکدهی بین صفحات
برای مسیریابی بین صفحات، میتوانید از کامپوننت <Link> استفاده کنید. <Link> یک کامپوننت داخلی در Next.js است که تگ HTML <a> را گسترش داده و ویژگیهای پیشبارگذاری (prefetching) و مسیریابی سمت کلاینت (client-side navigation) را فراهم میکند.
برای مثال، برای تولید یک لیست از پستهای وبلاگ، کامپوننت <Link> را از next/link وارد کرده و prop href را به کامپوننت بدهید:
// app/ui/post.tsx
import Link from 'next/link'
export default async function Post({ post }) {
const posts = await getPosts()
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
<Link> روش اصلی و توصیهشده برای مسیریابی بین مسیرها در برنامه Next.js شما است. با این حال، شما میتوانید از hook useRouter برای مسیریابی پیشرفتهتر نیز استفاده کنید.
طرحبندیها و الگوها (Layouts and Templates)
در Next.js، میتوانید از طرحبندیها (Layouts) و الگوها (Templates) برای مدیریت بخشهای مشترک صفحات مانند هدر، فوتر و نوار کناری استفاده کنید. این ویژگی به شما کمک میکند تا بخشهای مشابه را فقط یک بار تعریف کرده و در تمام صفحات استفاده کنید.
در Next.js چهار روش برای مسیریابی بین صفحات وجود دارد:
۱. استفاده از کامپوننت <Link>
۲. استفاده از هوک useRouter (برای کامپوننتهای کلاینت)
۳. استفاده از تابع redirect (برای کامپوننتهای سرور)
۴. استفاده از API تاریخچه (History API) بومی
این صفحه نحوه استفاده از هر یک از این گزینهها را بررسی کرده و به جزئیات نحوه عملکرد مسیریابی خواهد پرداخت.
کامپوننت <Link>
کامپوننت <Link> یک کامپوننت داخلی است که تگ HTML <a> را گسترش میدهد تا پیشبارگذاری (prefetching) و مسیریابی کلاینت-ساید بین صفحات را فراهم کند. این روش اصلی و توصیهشده برای مسیریابی بین صفحات در Next.js است.
برای استفاده از آن، کافی است آن را از next/link وارد کنید و یک prop به نام href به آن بدهید:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
سایر پروپهای اختیاری نیز وجود دارند که میتوانید به <Link> بدهید. برای اطلاعات بیشتر، به ارجاع API مراجعه کنید.
هوک useRouter()
هوک useRouter به شما این امکان را میدهد که بهصورت برنامهنویسی مسیرها را در کامپوننتهای کلاینت تغییر دهید.
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
توصیه: از کامپوننت <Link> برای مسیریابی بین صفحات استفاده کنید مگر اینکه نیاز خاصی به استفاده از useRouter داشته باشید.
تابع redirect
برای کامپوننتهای سرور، از تابع redirect به جای آن استفاده کنید.
import { redirect } from 'next/navigation'
async function fetchTeam(id: string) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}
export default async function Profile({
params,
}: {
params: Promise<{ id: string }>
}) {
const id = (await params).id
if (!id) {
redirect('/login')
}
const team = await fetchTeam(id)
if (!team) {
redirect('/join')
}
// ...
}
نکته مهم:
به طور پیشفرض، redirect کد وضعیت ۳۰۷ (تغییر موقتی مسیر) را باز میگرداند. وقتی در یک عملیات سرور استفاده شود، کد وضعیت ۳۰۳ (مشاهدهی سایر) که معمولاً برای هدایت به صفحه موفقیت پس از درخواست POST استفاده میشود، باز میگرداند.
تابع redirect بهطور داخلی یک خطا ایجاد میکند، بنابراین باید خارج از بلوکهای try/catch فراخوانی شود.
شما میتوانید از تابع redirect در کامپوننتهای کلاینت در فرآیند رندر استفاده کنید، اما در رویدادها نمیتوان از آن استفاده کرد. به جای آن میتوانید از هوک useRouter استفاده کنید.
تابع redirect همچنین از URLهای کامل پشتیبانی میکند و میتواند برای هدایت به لینکهای خارجی استفاده شود.
اگر میخواهید قبل از فرآیند رندر هدایت کنید، از next.config.js یا Middleware استفاده کنید.
استفاده از API تاریخچه بومی
Next.js به شما این امکان را میدهد که از متدهای بومی window.history.pushState و window.history.replaceState برای بهروزرسانی تاریخچه مرورگر بدون بارگذاری مجدد صفحه استفاده کنید.
متد pushState
از این متد برای اضافه کردن یک ورودی جدید به تاریخچه مرورگر استفاده کنید. کاربر میتواند به وضعیت قبلی برگردد. بهعنوان مثال، برای مرتبسازی یک لیست از محصولات:
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder: string) {
const params = new URLSearchParams(searchParams.toString())
params.set('sort', sortOrder)
window.history.pushState(null, '', `?${params.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
متد replaceState
از این متد برای جایگزینی ورودی جاری در تاریخچه مرورگر استفاده کنید. کاربر نمیتواند به وضعیت قبلی برگردد. بهعنوان مثال، برای تغییر زبان برنامه:
'use client'
import { usePathname } from 'next/navigation'
export function LocaleSwitcher() {
const pathname = usePathname()
function switchLocale(locale: string) {
const newPath = `/${locale}${pathname}`
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}
نحوه عملکرد مسیریابی و مسیریابی
روتر اپلیکیشن از رویکرد ترکیبی برای مسیریابی و مسیریابی استفاده میکند. در سرور، کد اپلیکیشن بهطور خودکار بر اساس بخشهای مسیر تقسیمبندی میشود. در کلاینت، Next.js بخشهای مسیر را پیشبارگذاری کرده و کش میکند. به این معنی که هنگام مسیریابی به یک مسیر جدید، مرورگر صفحه را دوباره بارگذاری نمیکند و فقط بخشهایی از مسیر که تغییر کردهاند مجدداً رندر میشوند، که تجربه مسیریابی و عملکرد را بهبود میبخشد.
۱. تقسیمبندی کد
تقسیمبندی کد به شما این امکان را میدهد که کد اپلیکیشن خود را به بستههای کوچکتر تقسیم کنید تا توسط مرورگر دانلود و اجرا شود. این کار باعث کاهش مقدار دادههای منتقلشده و زمان اجرای هر درخواست میشود و عملکرد را بهبود میبخشد.
کامپوننتهای سرور به اپلیکیشن شما این امکان را میدهند که بهطور خودکار بر اساس بخشهای مسیر تقسیمبندی شود. این به این معنی است که فقط کد لازم برای مسیر جاری در هنگام مسیریابی بارگذاری میشود.
۲. پیشبارگذاری
پیشبارگذاری به روشی گفته میشود که مسیری را پیش از بازدید کاربر، بهطور پیشزمینه بارگذاری کند.
۳. کش کردن
Next.js یک کش در حافظهی سمت کلاینت به نام “کش روتر” دارد. وقتی کاربران در اپلیکیشن جابجا میشوند، دادههای بخشهای مسیر پیشبارگذاریشده و مسیرهای بازدید شده در کش ذخیره میشود.
۴. رندرینگ جزئی
رندرینگ جزئی به این معنا است که فقط بخشهایی از مسیر که تغییر کردهاند مجدداً رندر میشوند و هر بخش مشترک حفظ میشود.
۵. مسیریابی نرم (Soft Navigation)
مسیریابی نرم به این معنا است که فقط بخشهای مسیر که تغییر کردهاند، مجدداً رندر میشوند و این امکان را میدهد که وضعیت React در حین مسیریابی حفظ شود.
۶. مسیریابی به عقب و جلو
بهطور پیشفرض، Next.js موقعیت اسکرول را در هنگام مسیریابی به عقب و جلو حفظ میکند و بخشهای مسیر را در کش روتر دوباره استفاده میکند.
۷. مسیریابی بین صفحات و اپلیکیشن
زمانی که از pages/ به app/ مهاجرت میکنید، روتر Next.js بهطور خودکار مسیریابی سخت بین این دو را مدیریت میکند.
مدیریت خطاها (Error Handling)
در Next.js، میتوانید با استفاده از صفحات مخصوص خطا، مانند 404.js یا 500.js، خطاها را مدیریت کنید. این صفحات به طور خودکار در صورت بروز خطا به کاربر نمایش داده میشوند.
خطاها را میتوان به دو دسته تقسیم کرد: خطاهای پیشبینیشده و استثناهای بدون پوشش
مدلسازی خطاهای پیشبینیشده به عنوان مقادیر برگشتی: از استفاده از try/catch برای خطاهای پیشبینیشده در Server Actions خودداری کنید. به جای آن از useActionState برای مدیریت این خطاها استفاده کرده و آنها را به کلاینت بازگردانید. استفاده از مرزهای خطا برای خطاهای غیرمنتظره: از مرزهای خطا با استفاده از فایلهای error.tsx و global-error.tsx برای مدیریت خطاهای غیرمنتظره و نمایش یک رابط کاربری پشتیبان استفاده کنید.
مدیریت خطاهای پیشبینیشده
خطاهای پیشبینیشده آن دسته از خطاهایی هستند که ممکن است در طول عملیات عادی برنامه رخ دهند، مانند خطاهای اعتبارسنجی فرم در سرور یا درخواستهای ناموفق. این خطاها باید به صورت واضح مدیریت شوند و به کلاینت بازگردانده شوند.
مدیریت خطاهای پیشبینیشده از Server Actions
از هوک useActionState برای مدیریت وضعیت Server Actions، از جمله مدیریت خطاها استفاده کنید. این روش از استفاده از بلوکهای try/catch برای خطاهای پیشبینیشده جلوگیری میکند و این خطاها باید به عنوان مقادیر برگشتی مدلسازی شوند نه استثناهای پرتابشده.
'use server'
import { redirect } from 'next/navigation'
export async function createUser(prevState: any, formData: FormData) {
const res = await fetch('https://...')
const json = await res.json()
if (!res.ok) {
return { message: 'Please enter a valid email' }
}
redirect('/dashboard')
}
سپس میتوانید اکشن خود را به هوک useActionState پاس داده و از وضعیت برگشتی برای نمایش پیام خطا استفاده کنید.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}
همچنین میتوانید از وضعیت برگشتی برای نمایش یک پیام توست از سمت کلاینت استفاده کنید.
مدیریت خطاهای پیشبینیشده از Server Components
زمانی که دادهها را در داخل یک Server Component بارگذاری میکنید، میتوانید از پاسخ برای رندر مشروط یک پیام خطا یا هدایت مجدد استفاده کنید.
export default async function Page() {
const res = await fetch(`https://...`)
const data = await res.json()
if (!res.ok) {
return 'There was an error.'
}
return '...'
}
استثناهای بدون پوشش
استثناهای بدون پوشش خطاهای غیرمنتظرهای هستند که نشاندهنده باگها یا مسائلی هستند که نباید در جریان عادی برنامه رخ دهند. این خطاها باید با پرتاب استثناها مدیریت شوند تا توسط مرزهای خطا گرفته شوند.
عمومی: خطاهای بدون پوشش را در زیر لایه ریشه با error.js مدیریت کنید. اختیاری: خطاهای بدون پوشش جزئی را با فایلهای nested error.js مدیریت کنید (مثلاً app/dashboard/error.js). غیر معمول: خطاهای بدون پوشش را در لایه ریشه با global-error.js مدیریت کنید.
استفاده از مرزهای خطا
Next.js از مرزهای خطا برای مدیریت استثناهای بدون پوشش استفاده میکند. مرزهای خطا خطاها را در کامپوننتهای فرزند خود گرفته و یک رابط کاربری پشتیبان به جای درخت کامپوننتی که خراب شده است، نمایش میدهند.
برای ایجاد یک مرز خطا، کافی است یک فایل error.tsx در داخل یک بخش مسیر ایجاد کرده و یک کامپوننت React صادر کنید:
'use client' // مرزهای خطا باید کامپوننتهای کلاینت باشند
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// ثبت خطا در یک سرویس گزارشدهی خطا
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// تلاش برای بازیابی با تلاش برای رندر مجدد بخش
() => reset()
}
>
Try again
</button>
</div>
)
}
اگر بخواهید خطاها به مرز خطای والد منتقل شوند، میتوانید هنگام رندر کامپوننت خطا، استثنا را پرتاب کنید.
مدیریت خطاها در مسیرهای تو در تو
خطاها به بالاترین مرز خطای والد خود منتقل میشوند. این امکان را میدهد که خطاها را به صورت جزئی مدیریت کنید و فایلهای error.tsx را در سطوح مختلف درخت مسیر قرار دهید.
مدیریت خطاهای جهانی
اگرچه کمتر رایج است، میتوانید خطاها را در لایه ریشه با استفاده از app/global-error.js که در دایرکتوری اصلی اپ قرار دارد، مدیریت کنید، حتی زمانی که از بینالمللیسازی استفاده میکنید. رابط کاربری خطای جهانی باید برچسبهای <html> و <body> خود را تعریف کند، زیرا در هنگام فعال شدن، لایه ریشه یا الگو را جایگزین میکند.
'use client' // مرزهای خطا باید کامپوننتهای کلاینت باشند
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
// global-error باید شامل برچسبهای html و body باشد
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
error.js
یک فایل خطا به شما این امکان را میدهد که خطاهای غیرمنتظره در زمان اجرا را مدیریت کرده و رابط کاربری پشتیبان را نمایش دهید.
فایل خاص error.js
'use client' // مرزهای خطا باید کامپوننتهای کلاینت باشند
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// ثبت خطا در سرویس گزارشدهی خطا
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// تلاش برای بازیابی با تلاش برای رندر مجدد بخش
() => reset()
}
>
Try again
</button>
</div>
)
}
فایل error.js یک بخش مسیر و فرزندان تو در توی آن را در یک React Error Boundary میپیچد. زمانی که یک خطا در داخل مرز خطا پرتاب میشود، کامپوننت خطا به عنوان رابط کاربری پشتیبان نمایش داده میشود.
نحوه عملکرد error.js
چیزهایی که باید بدانید:
ابزارهای توسعهدهنده React به شما این امکان را میدهند که مرزهای خطا را تغییر دهید تا وضعیتهای خطا را تست کنید. اگر بخواهید خطاها به مرز خطای والد منتقل شوند، میتوانید هنگام رندر کامپوننت خطا، استثنا را پرتاب کنید.
رابط کاربری بارگذاری و استریمینگ (Loading UI and Streaming)
فایل خاص loading.js به شما کمک میکند که یک رابط کاربری بارگذاری (Loading UI) مناسب را با استفاده از React Suspense ایجاد کنید. با استفاده از این روش، هنگام بارگذاری محتوای یک بخش مسیر (Route Segment)، میتوان یک وضعیت بارگذاری فوری از سمت سرور نمایش داد. زمانی که محتوای جدید بهطور کامل رندر شود، این محتوا بهطور خودکار جایگزین میشود.
رابط کاربری بارگذاری (Loading UI)
وضعیت بارگذاری فوری (Instant Loading States)
یک وضعیت بارگذاری فوری همان رابط کاربری پشتیبانی است که بلافاصله هنگام مسیریابی نمایش داده میشود. شما میتوانید نشانگرهای بارگذاری مانند Skeleton و Spinner را از پیش رندر کنید یا بخشی کوچک اما مهم از صفحه آینده را نمایش دهید (مانند عکس کاور یا عنوان). این کار باعث میشود کاربران متوجه شوند که برنامه در حال پردازش است و تجربه بهتری داشته باشند.
برای ایجاد یک وضعیت بارگذاری، کافی است یک فایل loading.js را در یک پوشه اضافه کنید.
فایل خاص loading.js
export default function Loading() {
// شما میتوانید هر رابط کاربری را درون Loading اضافه کنید، از جمله یک Skeleton.
return <LoadingSkeleton />
}
در همان پوشه، loading.js داخل layout.js قرار میگیرد و بهطور خودکار page.js و تمامی فرزندان آن را درون یک Suspense boundary قرار میدهد.
نکات مهم درباره loading.js
مسیریابی فوری انجام میشود حتی اگر از رویکرد سرور-محور استفاده کنید.
مسیریابی قابل قطع است، یعنی اگر مسیر تغییر کند، نیازی نیست که تا بارگیری کامل مسیر قبلی صبر کنید.
چینشهای مشترک (Shared Layouts) تعاملی باقی میمانند در حالی که بخشهای جدید مسیر بارگذاری میشوند.
توصیه: از loading.js برای بخشهای مسیر (Layouts و Pages) استفاده کنید، زیرا Next.js این قابلیت را بهینهسازی کرده است.
استریمینگ با Suspense
علاوه بر loading.js، شما میتوانید بهصورت دستی Suspense Boundaries را برای کامپوننتهای خود ایجاد کنید. App Router در Next.js از استریمینگ همراه با Suspense پشتیبانی میکند.
نکات مهم درباره استریمینگ
برخی از مرورگرها پاسخهای استریمشده را بافر میکنند، بنابراین ممکن است محتوای استریمشده را مشاهده نکنید تا زمانی که پاسخ بیش از 1024 بایت باشد. این مسئله بیشتر روی برنامههای ساده مثل “Hello World” تأثیر دارد، اما در برنامههای واقعی معمولاً مشکلی ایجاد نمیکند.
استریمینگ چیست؟
برای درک نحوه کار استریمینگ در React و Next.js، بهتر است ابتدا با رندر سمت سرور (Server-Side Rendering – SSR) و محدودیتهای آن آشنا شویم.
روند کار SSR سنتی
در روش SSR، برای اینکه یک کاربر بتواند صفحه را ببیند و با آن تعامل داشته باشد، چند مرحله باید انجام شود:
تمام دادههای صفحه از سرور دریافت میشود.
سرور HTML صفحه را رندر میکند.
HTML، CSS، و JavaScript به سمت کلاینت ارسال میشوند.
یک رابط کاربری غیرتعاملی نمایش داده میشود.
در نهایت، React صفحه را هیدراته (Hydrate) کرده و آن را تعاملی میکند.
این مراحل بهصورت پیاپی (Sequential) و مسدودکننده (Blocking) هستند. به این معنا که تا زمانی که تمامی دادهها بارگیری نشده باشند، سرور نمیتواند HTML را رندر کند و در سمت کلاینت نیز React نمیتواند هیدراته شود تا زمانی که تمام کامپوننتهای صفحه دانلود شوند.
استریمینگ چگونه کار میکند؟
استریمینگ این امکان را فراهم میکند که HTML صفحه به بخشهای کوچکتر تقسیم شده و بهصورت تدریجی از سرور به کلاینت ارسال شود.
این ویژگی باعث میشود بخشهایی از صفحه سریعتر نمایش داده شوند، بدون اینکه منتظر بارگیری تمامی دادهها بمانند.
مزایای استریمینگ در مدل کامپوننتی React
هر کامپوننت را میتوان بهعنوان یک بخش مستقل در نظر گرفت.
کامپوننتهای با اولویت بالاتر (مثلاً اطلاعات محصول) یا کامپوننتهایی که وابسته به داده نیستند (مثلاً Layout) میتوانند زودتر ارسال شوند.
کامپوننتهای با اولویت پایینتر (مثلاً بررسیهای کاربران و محصولات مرتبط) بعد از دریافت دادههای موردنیاز، به همان درخواست سرور اضافه شده و ارسال میشوند.
در نتیجه:
استریمینگ میتواند زمان تا اولین بایت (TTFB) و اولین محتوای قابل نمایش (FCP) را کاهش دهد و همچنین زمان تا تعامل (TTI) را بهبود بخشد، بهویژه در دستگاههای کندتر.
مثال از استفاده <Suspense>
کامپوننت <Suspense> با احاطه کردن کامپوننتهای دارای عملیات غیرهمزمان (مانند درخواستهای fetch) کار میکند. در این مدت، یک رابط کاربری پشتیبان (Fallback UI) مانند Skeleton یا Spinner نمایش داده میشود و پس از تکمیل عملیات، کامپوننت موردنظر جایگزین میشود.
import { Suspense } from 'react'
import { PostFeed, Weather } from './Components'
export default function Posts() {
return (
<section>
<Suspense fallback={<p>Loading feed...</p>}>
<PostFeed />
</Suspense>
<Suspense fallback={<p>Loading weather...</p>}>
<Weather />
</Suspense>
</section>
)
}
مزایای استفاده از <Suspense>
استریمینگ در رندر سمت سرور → ارسال تدریجی HTML از سرور به کلاینت
هیدراتهسازی انتخابی (Selective Hydration) → React ابتدا کامپوننتهای مهمتر را تعاملی میکند
کاهش زمان بارگذاری و بهبود عملکرد
برای مثالهای بیشتر، میتوانید مستندات React Suspense را مطالعه کنید.
بهینهسازی سئو (SEO) در استریمینگ
Next.js قبل از استریمکردن UI، منتظر fetch دادههای داخل generateMetadata میماند. این تضمین میکند که قسمت اول پاسخ streamed شامل تگهای <head> باشد.
از آنجایی که استریمینگ در سمت سرور انجام میشود، تأثیری بر SEO ندارد.
میتوانید از ابزار Rich Results Test گوگل استفاده کنید تا ببینید که صفحه شما چگونه توسط موتور جستجوی گوگل مشاهده و ایندکس میشود.
کدهای وضعیت در استریمینگ
در استریمینگ، کد وضعیت 200 بازگردانده میشود که نشاندهنده موفقیت درخواست است.
اگر نیاز به نمایش خطا باشد، سرور میتواند خطا را درون محتوای استریمشده ارسال کند (مثلاً هنگام استفاده از redirect یا notFound). اما از آنجا که هدرهای پاسخ قبلاً به کلاینت ارسال شدهاند، امکان تغییر کد وضعیت وجود ندارد.
هدایت مجدد (Redirecting)
در Next.js روشهای مختلفی برای هدایت مجدد (Redirect) وجود دارد. در این راهنما به بررسی این روشها، موارد استفادهی آنها و نحوهی مدیریت تعداد زیادی از هدایتهای مجدد میپردازیم.
|
API
|
هدف
|
محل استفاده
|
کد وضعیت
|
|
redirect
|
هدایت مجدد کاربر پس از یک تغییر یا رویداد
|
Server Components، Server Actions، Route Handlers
|
307 موقت) یا)
|
|
permanentRedirect
|
هدایت مجدد دائمی کاربر پس از یک تغییر یا رویداد
|
Server Components، Server Actions، Route Handlers
|
308 (دائمی)
|
|
useRouter
|
هدایت سمت کاربر (Client-side)
|
Event Handlers در Client Components
|
N/A
|
|
تنظیمات next.config.js
|
هدایت درخواست ورودی بر اساس مسیر
|
فایل next.config.js
|
307 (موقت) یا 308 (دائمی)
|
|
NextResponse.redirect
|
هدایت بر اساس شرایط خاص
|
Middleware
|
هر وضعیت کدی
|
تابع redirect
تابع redirect امکان هدایت کاربر به یک آدرس جدید را فراهم میکند. این تابع را میتوان در Server Components، Route Handlers، و Server Actions استفاده کرد.
مثلاً بعد از ایجاد یک پست جدید:
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id: string) {
try {
// فراخوانی پایگاه داده
} catch (error) {
// مدیریت خطاها
}
revalidatePath('/posts') // بهروزرسانی کش پستها
redirect(`/post/${id}`) // انتقال کاربر به صفحهی پست جدید
}
نکات مهم:
مقدار پیشفرض redirect، وضعیت 307 (موقت) است. در Server Actions مقدار 303 (See Other) را برمیگرداند که معمولاً برای هدایت بعد از درخواست POST استفاده میشود.
این تابع درون try/catch قرار نمیگیرد، زیرا بهصورت داخلی یک خطا ایجاد میکند.
این تابع را میتوان در Client Components در حین پردازش Render استفاده کرد، اما در Event Handlers نباید از آن استفاده شود. برای این موارد از useRouter استفاده کنید.
امکان هدایت به آدرسهای مطلق و لینکهای خارجی را نیز دارد.
اگر نیاز به هدایت قبل از پردازش صفحه دارید، از next.config.js یا Middleware استفاده کنید.
تابع permanentRedirect
تابع permanentRedirect برای هدایت دائمی کاربران به یک آدرس جدید استفاده میشود. مثلاً زمانی که URL یک پروفایل کاربر بعد از تغییر نام کاربری تغییر کند:
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username: string, formData: FormData) {
try {
// فراخوانی پایگاه داده
} catch (error) {
// مدیریت خطاها
}
revalidateTag('username') // بهروزرسانی تمام ارجاعات به نام کاربری
permanentRedirect(`/profile/${username}`) // هدایت به صفحهی جدید پروفایل
}
نکات مهم:
مقدار پیشفرض permanentRedirect، وضعیت 308 (دائمی) است.
امکان هدایت به آدرسهای مطلق و لینکهای خارجی را دارد.
اگر نیاز به هدایت قبل از پردازش صفحه دارید، از next.config.js یا Middleware استفاده کنید.
استفاده از useRouter برای هدایت سمت کاربر
اگر در یک Client Component نیاز به هدایت در یک Event Handler دارید، میتوانید از useRouter استفاده کنید:
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
داشبورد
</button>
)
نکات مهم:
اگر هدایت نیاز به تعامل برنامهنویسی ندارد، از کامپوننت استفاده کنید.
هدایت از طریق next.config.js
در فایل next.config.js میتوان مسیرهای ورودی را به مسیرهای دیگر هدایت کرد. این روش برای زمانی که ساختار URL تغییر کرده یا لیستی از مسیرهای مشخص از قبل تعیین شدهاند، مناسب است.
مثال:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async redirects() {
return [
{
source: '/about',
destination: '/',
permanent: true,
},
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
export default nextConfig
نکات مهم:
هدایتها میتوانند وضعیت 307 (موقت) یا 308 (دائمی) داشته باشند.
در برخی پلتفرمها، محدودیت تعداد هدایتها وجود دارد. مثلاً در Vercel حداکثر 1024 هدایت مجاز است. برای تعداد بیشتر، از Middleware استفاده کنید.
هدایت با NextResponse.redirect در Middleware
در Middleware میتوان قبل از پردازش یک درخواست، مسیر هدایت را تعیین کرد. این روش برای هدایتهای وابسته به شرایط مانند احراز هویت مناسب است:
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)
if (isAuthenticated) {
return NextResponse.next()
}
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
نکات مهم:
Middleware بعد از هدایتهای next.config.js و قبل از پردازش صفحه اجرا میشود.
مدیریت تعداد زیاد هدایتها
برای بیش از 1000 هدایت، بهتر است از Middleware همراه با پایگاه دادهی سریع (مانند Redis) استفاده کنید. همچنین میتوان از Bloom Filter برای بررسی سریع مسیرهای هدایت استفاده کرد.
این روش از دو بخش تشکیل شده است:
ذخیرهی لیست هدایتها در پایگاه داده
بهینهسازی فرآیند جستجوی مسیرهای هدایت
بهجای بارگذاری تمام هدایتها، ابتدا مسیر در Bloom Filter بررسی شده و سپس در صورت وجود، مسیر نهایی از پایگاه داده خوانده میشود. این روش سرعت پردازش را بهبود میبخشد.
با این روشها میتوان مدیریت مؤثر و بهینهای بر هدایتهای صفحات در Next.js داشت.
گروههای مسیر (Route Groups)
در پوشهی app، پوشههای تو در تو معمولاً به مسیرهای URL نگاشت میشوند. اما میتوان یک پوشه را به عنوان گروه مسیر (Route Group) علامتگذاری کرد تا از درج نام آن در مسیر URL جلوگیری شود.
این قابلیت به شما این امکان را میدهد که بخشهای مسیر و فایلهای پروژه را به صورت منطقی سازماندهی کنید، بدون اینکه بر ساختار URL تأثیر بگذارد.
موارد استفاده از گروههای مسیر:
سازماندهی مسیرها بر اساس بخشهای سایت، هدف یا تیمهای توسعه
ایجاد لایههای (Layout) تو در تو در یک سطح مسیر مشخص:
ایجاد چندین لایهی تو در تو در یک سطح مسیر
اضافه کردن یک لایه به زیرمجموعهای از مسیرها در یک بخش مشترک
اضافه کردن صفحهی لودینگ (Loading Skeleton) به یک مسیر خاص
نحوهی تعریف گروه مسیر
برای تعریف یک گروه مسیر، کافی است نام پوشه را داخل پرانتز قرار دهید:
(folderName)
مثالها
۱. سازماندهی مسیرها بدون تأثیر بر URL
اگر بخواهید مسیرهای خود را بدون تأثیر بر URL سازماندهی کنید، میتوانید یک گروه مسیر ایجاد کنید تا مسیرهای مرتبط در کنار هم نگه داشته شوند. در این حالت، پوشههایی که نامشان در پرانتز قرار دارد، از URL حذف میشوند.
مثال:
app/ ├── (marketing)/ │ ├── about/ │ │ ├── page.js → /about │ ├── contact/ │ │ ├── page.js → /contact ├── (shop)/ │ ├── products/ │ │ ├── page.js → /products │ ├── cart/ │ │ ├── page.js → /cart
در مثال بالا، مسیرهای (marketing) و (shop) در ساختار پروژه وجود دارند، اما نام آنها در URL ظاهر نمیشود.
۲. ایجاد چندین لایهی تو در تو
اگر بخواهید چندین لایه (Layout) در یک سطح مسیر داشته باشید، میتوانید داخل پوشههای گروه مسیر یک فایل layout.js اضافه کنید.
app/ ├── (marketing)/ │ ├── layout.js → قالب اختصاصی برای گروه marketing │ ├── about/ │ │ ├── page.js → /about ├── (shop)/ │ ├── layout.js → قالب اختصاصی برای گروه shop │ ├── cart/ │ │ ├── page.js → /cart
در این ساختار، هر گروه مسیر، لایهی مخصوص خود را دارد.
۳. اختصاص یک لایه به برخی از مسیرها
برای اعمال یک لایهی خاص به برخی از مسیرها، میتوان مسیرهایی که باید از این لایه استفاده کنند را در یک گروه مسیر قرار داد. مسیرهای خارج از این گروه از این لایه استفاده نخواهند کرد.
app/ ├── (shop)/ │ ├── layout.js → لایهی مشترک برای حساب کاربری و سبد خرید │ ├── account/ │ │ ├── page.js → /account │ ├── cart/ │ │ ├── page.js → /cart ├── checkout/ │ ├── page.js → /checkout (بدون لایهی گروه shop)
در این ساختار، مسیرهای /account و /cart از layout.js گروه shop استفاده میکنند، اما /checkout این لایه را ندارد.
۴. افزودن صفحهی لودینگ (Loading Skeleton) به یک مسیر خاص
برای نمایش یک صفحهی لودینگ خاص برای یک مسیر مشخص، میتوان از یک گروه مسیر جدید استفاده کرد و loading.tsx را داخل آن قرار داد.
app/ ├── dashboard/ │ ├── (overview)/ │ │ ├── loading.tsx → نمایش فقط در /dashboard/overview │ │ ├── page.tsx → /dashboard/overview
در این مثال، صفحهی loading.tsx فقط روی مسیر /dashboard/overview اعمال میشود و مسیرهای دیگر dashboard تحت تأثیر قرار نمیگیرند.
۵. ایجاد چندین لایهی ریشهای (Root Layout)
اگر بخواهید چندین بخش از سایت تجربهی کاربری کاملاً متفاوتی داشته باشند، میتوانید چندین لایهی Root Layout ایجاد کنید.
برای این کار:
فایل layout.js در سطح بالا را حذف کنید
داخل هر گروه مسیر یک layout.js اضافه کنید
تگهای <html> و <body> را در هر root layout اضافه کنید
app/ ├── (marketing)/ │ ├── layout.js → لایهی مخصوص بخش بازاریابی │ ├── about/ │ │ ├── page.js → /about ├── (shop)/ │ ├── layout.js → لایهی مخصوص بخش فروشگاه │ ├── cart/ │ │ ├── page.js → /cart
در این ساختار:
مسیرهای داخل (marketing) از layout.js مخصوص خودشان استفاده میکنند.
مسیرهای داخل (shop) از layout.js مخصوص خودشان استفاده میکنند.
نکات مهم
نام گروههای مسیر فقط برای سازماندهی استفاده میشود و هیچ تأثیری بر URL ندارد.
نباید دو مسیر یکسان در گروههای مختلف به یک URL یکسان اشاره کنند. مثلا:
(marketing)/about/page.js و (shop)/about/page.js هردو به مسیر /about اشاره میکنند که باعث خطا میشود.
اگر از چندین Root Layout استفاده میکنید و فایل layout.js سطح بالا را حذف کردهاید، فایل اصلی page.js صفحه خانه باید در یکی از گروههای مسیر تعریف شود.
جابجایی بین Root Layoutهای مختلف، باعث یک بارگذاری کامل صفحه (Full Page Load) میشود.
مثلاً حرکت از /cart که از (shop)/layout.js استفاده میکند به /blog که از (marketing)/layout.js استفاده میکند، باعث بارگذاری مجدد کل صفحه خواهد شد.
مسیرهای داینامیک (Dynamic Routes)
وقتی که از قبل نام دقیق بخشهای مسیر را نمیدانید و میخواهید مسیرهایی را بر اساس دادههای داینامیک ایجاد کنید، میتوانید از بخشهای داینامیک (Dynamic Segments) استفاده کنید. این بخشها در زمان درخواست (Request Time) مقداردهی شده یا در زمان ساخت (Build Time) از پیش رندر (Prerender) میشوند.
نحوهی تعریف بخشهای داینامیک
برای ایجاد یک بخش داینامیک، نام پوشه را داخل براکت [ ] قرار دهید:
[folderName]
مثالهایی از نامگذاری صحیح:
[id]
[slug]
این بخشهای داینامیک به عنوان prop پارامترها (params) به توابع layout، page، route، و generateMetadata ارسال میشوند.
مثال
فرض کنید یک وبلاگ دارید که مسیر مقالات آن به این صورت تعریف شده است:
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug
return <div>My Post: {slug}</div>
}
در اینجا، [slug] یک بخش داینامیک است که مقدار آن برای هر مقاله متفاوت خواهد بود.
مثال از نحوهی دریافت مقدار slug در فایل page.tsx:
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug
return <div>My Post: {slug}</div>
}
در مسیر app/blog/[slug]/page.js مقدار params به این صورت خواهد بود:
برای مسیر /blog/a مقدار params برابر با { slug: ‘a’ } است.
برای مسیر /blog/b مقدار params برابر با { slug: ‘b’ } است.
برای مسیر /blog/c مقدار params برابر با { slug: ‘c’ } است.
برای یادگیری نحوهی ایجاد پارامترهای داینامیک در زمان ساخت، به مستندات generateStaticParams() مراجعه کنید.
نکات مهم
params در نسخههای جدید بهصورت Promise است. بنابراین باید از async/await یا React use برای دریافت مقدار آن استفاده کنید.
در نسخه 14 و قبلتر، params یک prop سینکرون بود. در نسخه 15 همچنان میتوان آن را سینکرون دریافت کرد، اما این رفتار در آینده حذف خواهد شد.
بخشهای داینامیک در پوشهی app معادل مسیرهای داینامیک (Dynamic Routes) در پوشهی pages هستند.
تولید پارامترهای داینامیک در زمان ساخت (Generating Static Params)
تابع generateStaticParams میتواند همراه با بخشهای داینامیک استفاده شود تا مسیرها در زمان بیلد (Build Time) بهصورت استاتیک ساخته شوند، به جای اینکه در زمان درخواست (Request Time) پردازش شوند.
مثال:
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
مزیت اصلی استفاده از generateStaticParams:
اگر داخل این تابع از fetch استفاده شود، دادهها بهصورت هوشمند کش (Memoized) میشوند.
اگر همان درخواست fetch در چندین بخش مختلف (مثل generateStaticParams، Layouts، و Pages) تکرار شود، فقط یک بار اجرا میشود.
این کار باعث کاهش زمان بیلد و بهینهتر شدن عملکرد برنامه میشود.
برای اطلاعات بیشتر، میتوانید مستندات generateStaticParams را مطالعه کنید.
مسیرهای همهگیر (Catch-all Segments)
گاهی نیاز دارید که چندین سطح از مسیرها را بهصورت داینامیک مدیریت کنید. در این حالت، میتوان یک بخش داینامیک همهگیر ایجاد کرد.
برای این کار، باید سه نقطه (…) را داخل براکتها قرار دهید:
[...folderName]
مثال:
app/shop/[...slug]/page.js
این مسیر میتواند هر تعداد سطح از مسیرهای داینامیک را مدیریت کند.
در مسیر app/shop/[…slug]/page.js مقدار params به این صورت خواهد بود:
برای مسیر /shop/a مقدار params برابر با { slug: [‘a’] } است.
برای مسیر /shop/a/b مقدار params برابر با { slug: [‘a’, ‘b’] } است.
برای مسیر /shop/a/b/c مقدار params برابر با { slug: [‘a’, ‘b’, ‘c’] } است.
مسیرهای همهگیر اختیاری (Optional Catch-all Segments)
گاهی ممکن است مسیر همهگیر وجود داشته باشد، اما استفاده از آن اختیاری باشد. در این صورت، باید براکتها را دو لایهای بنویسید:
[[...folderName]]
مثال:
app/shop/[[...slug]]/page.js
این مسیر علاوه بر حالتهای معمول، مسیر /shop را هم مدیریت میکند.
در مسیر app/shop/[[…slug]]/page.js مقدار params به این صورت خواهد بود:
برای مسیر /shop مقدار params برابر با { slug: undefined } است.
برای مسیر /shop/a مقدار params برابر با { slug: [‘a’] } است.
برای مسیر /shop/a/b مقدار params برابر با { slug: [‘a’, ‘b’] } است.
برای مسیر /shop/a/b/c مقدار params برابر با { slug: [‘a’, ‘b’, ‘c’] } است.
تفاوت بین Catch-all و Optional Catch-all
در حالت عادی ([…slug]) مسیر /shop پشتیبانی نمیشود.
در حالت اختیاری ([[…slug]]) مسیر /shop هم قابل دسترس است.
استفاده از TypeScript برای مسیرهای داینامیک
هنگام استفاده از TypeScript، میتوان نوع پارامترهای مسیر را مشخص کرد.
مثال در page.tsx برای دریافت مقدار slug:
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
return <h1>My Page</h1>
}
انواع پارامترها در TypeScript:
در مسیر app/blog/[slug]/page.js مقدار params به صورت { slug: string } خواهد بود.
در مسیر app/shop/[…slug]/page.js مقدار params به صورت { slug: string[] } خواهد بود، یعنی میتواند شامل یک آرایه از مقادیر باشد.
در مسیر app/shop/[[…slug]]/page.js مقدار params به صورت { slug?: string[] } تعریف میشود، که به این معناست که مقدار slug میتواند وجود نداشته باشد یا شامل یک آرایه از مقادیر باشد.
در مسیر app/[categoryId]/[itemId]/page.js مقدار params به صورت { categoryId: string, itemId: string } خواهد بود، یعنی هر دو مقدار categoryId و itemId به عنوان پارامترهای اجباری در مسیر تعریف شدهاند.
نکته: در آینده، پلاگین TypeScript این تنظیمات را بهصورت خودکار انجام خواهد داد.
مسیرهای موازی (Parallel Routes)
مسیرهای موازی به شما این امکان را میدهند که بهطور همزمان یا مشروط، یک یا چند صفحه را درون یک layout رندر کنید. این ویژگی برای بخشهای بسیار داینامیک یک برنامه مانند داشبوردها و فیدهای شبکههای اجتماعی مفید است.
بهعنوان مثال، در یک داشبورد میتوان از مسیرهای موازی برای رندر همزمان صفحات team و analytics استفاده کرد.
اسلاتها (Slots)
مسیرهای موازی با استفاده از اسلاتهای نامگذاریشده ایجاد میشوند. اسلاتها با استفاده از قرارداد @folder تعریف میشوند. بهعنوان مثال، در ساختار زیر دو اسلات @analytics و @team تعریف شدهاند:
app/ ├── layout.tsx ├── @analytics/ │ ├── page.tsx ├── @team/ │ ├── page.tsx
این اسلاتها بهعنوان prop به layout والد مشترک ارسال میشوند. حالا کامپوننت موجود در app/layout.tsx این دو prop را دریافت کرده و بهصورت همزمان در کنار children رندر میکند:
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
نکات مهم:
اسلاتها بخشی از مسیر (segment) نیستند و روی ساختار URL تأثیر نمیگذارند. مثلا در مسیر /@analytics/views، آدرس نهایی /views خواهد بود، زیرا @analytics تنها یک slot است.
اسلاتها با کامپوننتهای Page معمولی ترکیب میشوند تا صفحه نهایی مرتبط با یک route segment ساخته شود.
نمیتوان اسلاتهای استاتیک و داینامیک را در یک سطح مسیر ترکیب کرد. اگر یکی از اسلاتها داینامیک باشد، بقیه اسلاتهای آن سطح هم باید داینامیک باشند.
حالت فعال (Active State) و ناوبری
بهطور پیشفرض، Next.js وضعیت فعال (یا صفحه فرعی) را برای هر اسلات حفظ میکند. اما محتوای داخل یک اسلات، بسته به نوع ناوبری، تغییر میکند:
ناوبری نرم (Soft Navigation): در ناوبری سمت کلاینت، فقط محتوای داخل اسلات تغییر میکند و اسلاتهای دیگر همان محتوای فعال خود را حفظ میکنند، حتی اگر با URL فعلی مطابقت نداشته باشند.
ناوبری سخت (Hard Navigation): پس از بارگذاری کامل صفحه (مثل رفرش مرورگر)، Next.js نمیتواند وضعیت فعال اسلاتهایی را که با URL فعلی مطابقت ندارند، تشخیص دهد. در این حالت، یک فایل default.js برای اسلاتهای نامرتبط رندر میشود. اگر default.js وجود نداشته باشد، خطای ۴۰۴ نمایش داده میشود.
default.js
برای مدیریت اسلاتهای نامرتبط، میتوان یک فایل default.js ایجاد کرد تا بهعنوان فراخوانی پیشفرض (fallback) در هنگام بارگذاری اولیه یا رفرش کامل صفحه عمل کند.
بهعنوان مثال، در ساختار زیر، اسلات @team دارای یک صفحه /settings است، اما اسلات @analytics چنین صفحهای ندارد:
app/ ├── layout.tsx ├── @analytics/ │ ├── page.tsx ├── @team/ │ ├── page.tsx app/ ├── layout.tsx ├── @analytics/ │ ├── default.tsx ├── @team/ │ ├── settings.tsx
در این حالت:
هنگام ناوبری به /settings، اسلات @team صفحه /settings را رندر میکند، در حالی که اسلات @analytics محتوای فعال خود را حفظ میکند.
اما پس از رفرش صفحه، Next.js فایل default.js را برای @analytics رندر خواهد کرد. اگر این فایل وجود نداشته باشد، خطای ۴۰۴ نمایش داده میشود.
نکته:
چون children یک اسلات ضمنی است، لازم است یک default.js برای آن نیز ایجاد شود تا در صورت از بین رفتن وضعیت فعال صفحه والد، یک مقدار جایگزین نمایش داده شود.
تابع useSelectedLayoutSegment(s)
تابعهای useSelectedLayoutSegment و useSelectedLayoutSegments با دریافت parallelRoutesKey، مسیر فعال را درون یک اسلات مشخص بازیابی میکنند.
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
}
مثلا اگر کاربر به مسیر /login برود، مقدار loginSegment برابر با “login” خواهد شد.
مثالها
مسیرهای شرطی
میتوان مسیرهای موازی را بهصورت شرطی رندر کرد. مثلا برای نمایش صفحات مختلف داشبورد بسته به نقش کاربر (admin یا user):
import { checkUserRole } from '@/lib/auth'
export default function Layout({
user,
admin,
}: {
user: React.ReactNode
admin: React.ReactNode
}) {
const role = checkUserRole()
return role === 'admin' ? admin : user
}
گروههای تب (Tab Groups)
با قرار دادن یک layout داخل یک اسلات، میتوان به کاربران اجازه داد که اسلات را بهصورت مستقل مرور کنند. مثلا برای ایجاد تبها:
import Link from 'next/link'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Link href="/page-views">Page Views</Link>
<Link href="/visitors">Visitors</Link>
</nav>
<div>{children}</div>
</>
)
}
مودالها (Modals) و مسیرهای موازی
میتوان از مسیرهای موازی همراه با Intercepting Routes برای ایجاد مودالهای عمیق (Deep Linking Modals) استفاده کرد. مثلا یک کاربر میتواند مودال ورود را از طریق ناوبری سمت کلاینت باز کند، یا به /login در URL مراجعه کند.
اجرای این الگو:
مسیر /login را ایجاد کنید که صفحه اصلی ورود را نمایش دهد.
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
در اسلات @auth، یک فایل default.js ایجاد کنید که مقدار null را بازگرداند.
export default function Default() {
return null
}
درون @auth، مسیر /login را رهگیری کنید و مودال را نمایش دهید:
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
باز و بسته کردن مودال:
هنگام کلیک روی لینک ورود، مودال باز شده و URL به /login تغییر میکند.
اگر صفحه رفرش شود، کاربر مستقیماً به صفحه ورود منتقل خواهد شد.
import Link from 'next/link'
export default function Layout({
auth,
children,
}: {
auth: React.ReactNode
children: React.ReactNode
}) {
return (
<>
<nav>
<Link href="/login">Open modal</Link>
</nav>
<div>{auth}</div>
<div>{children}</div>
</>
)
}
برای بستن مودال:
'use client'
import { useRouter } from 'next/navigation'
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter()
return (
<>
<button onClick={() => router.back()}>Close modal</button>
<div>{children}</div>
</>
)
}
بارگذاری و مدیریت خطا
مسیرهای موازی میتوانند بهطور مستقل استریم شوند، بنابراین میتوان برای هر مسیر، حالت بارگذاری و خطای جداگانه تعریف کرد.
مسیریابی در حال ردگیری (Intercepting Routes)
مسیریابی در حال ردگیری به شما این امکان را میدهد که مسیری از بخش دیگری از اپلیکیشن خود را در قالب کنونی بارگذاری کنید. این الگوی مسیریابی زمانی مفید است که بخواهید محتوای یک مسیر را بدون تغییر زمینه (context) کاربر نمایش دهید.
برای مثال، هنگامی که روی عکسی در فید کلیک میکنید، میتوانید تصویر را در یک مودال نمایش دهید و فید را روی آن لایهبندی کنید. در این حالت، Next.js مسیر /photo/123 را ردگیری میکند، URL را مخفی میکند و آن را روی /feed قرار میدهد.
مسیریابی در حال ردگیری: ناوبری نرم (Soft Navigation)
اما هنگامی که با کلیک روی یک URL قابل اشتراکگذاری یا با رفرش صفحه به سمت عکس میروید، باید صفحه کامل عکس نمایش داده شود نه مودال. در این حالت، نباید مسیریابی در حال ردگیری رخ دهد.
مسیریابی در حال ردگیری: ناوبری سخت (Hard Navigation)
قرارداد (Convention) مسیریابی در حال ردگیری میتواند با استفاده از قرارداد ( .. ) تعریف شود که مشابه قرارداد مسیر نسبی ../ است، اما برای segment ها.
برای این منظور میتوانید از موارد زیر استفاده کنید:
(.) برای تطبیق segment ها در همان سطح
( .. ) برای تطبیق segment ها در یک سطح بالاتر
( .. )( .. ) برای تطبیق segment ها در دو سطح بالاتر
( … ) برای تطبیق segment ها از دایرکتوری ریشه اپلیکیشن
برای مثال، میتوانید بخش عکس را از داخل بخش فید با ایجاد یک دایرکتوری ( .. )photo ردگیری کنید.
ساختار پوشههای مسیریابی در حال ردگیری
توجه داشته باشید که قرارداد ( .. ) بر اساس segment های مسیر است نه سیستم فایل.
مثالها
مودالها (Modals)
مسیریابی در حال ردگیری میتواند همراه با مسیرهای موازی (Parallel Routes) برای ایجاد مودالها استفاده شود. این ترکیب به شما کمک میکند تا چالشهای رایج هنگام ساخت مودالها را حل کنید، از جمله:
- اشتراکگذاری محتوای مودال از طریق URL.
- حفظ زمینه زمانی که صفحه رفرش میشود، بدون بستن مودال.
- بستن مودال در ناوبری به عقب، به جای رفتن به مسیر قبلی.
- باز کردن مجدد مودال در ناوبری به جلو.
تصور کنید یک الگوی UI داریم که کاربر میتواند از گالری، با استفاده از ناوبری سمت کلاینت، یک مودال عکس را باز کند یا مستقیماً از طریق یک URL قابل اشتراکگذاری به صفحه عکس برود.
در مثال بالا، مسیر به بخش عکس میتواند از مطابقت ( .. ) استفاده کند چون @modal یک slot است و نه یک segment. این به این معنی است که مسیر عکس فقط یک سطح بالاتر از segment است، علیرغم اینکه دو سطح بالاتر از سیستم فایل قرار دارد.
نکات مفید:
نمونههای دیگری که میتوان استفاده کرد شامل باز کردن یک مودال لاگین در نوار ناوبری بالایی است، در حالی که صفحه مخصوص /login هم وجود دارد، یا باز کردن یک سبد خرید در یک مودال کناری.
مدیریت مسیرها (Route Handlers)
مدیریت مسیرها به شما این امکان را میدهد که برای هر مسیر، هندلرهای درخواست سفارشی با استفاده از Web Request و Response APIs ایجاد کنید.
فایل ویژه Route.js
نکته مهم: مدیریت مسیرها تنها در داخل دایرکتوری app در دسترس است. اینها معادل API Routes در داخل دایرکتوری pages هستند، به این معنی که نیازی به استفاده همزمان از API Routes و Route Handlers نیست.
قرارداد (Convention)
مدیریت مسیرها در یک فایل route.js یا route.ts داخل دایرکتوری app تعریف میشود:
app/api/route.ts
این فایل میتواند از نوع TypeScript باشد:
export async function GET(request: Request) {}
مدیریت مسیرها میتواند در هر جایی از داخل دایرکتوری app تو در تو قرار بگیرد، مشابه فایلهای page.js و layout.js. اما نباید یک فایل route.js در همان سطح segment مسیر به عنوان page.js قرار گیرد.
متدهای پشتیبانی شده HTTP
متدهای HTTP زیر پشتیبانی میشوند: GET، POST، PUT، PATCH، DELETE، HEAD و OPTIONS. اگر متدی که پشتیبانی نمیشود فراخوانی شود، Next.js یک پاسخ 405 Method Not Allowed باز میگرداند.
گسترش APIs NextRequest و NextResponse
علاوه بر پشتیبانی از APIهای بومی Request و Response، Next.js اینها را با NextRequest و NextResponse گسترش داده است تا توابع کمکی مناسب برای استفادههای پیشرفتهتر را فراهم کند.
رفتار (Behavior)
کش (Caching)
مدیریت مسیرها به طور پیشفرض کش نمیشوند. با این حال، میتوانید کش کردن را برای متدهای GET فعال کنید. سایر متدهای HTTP پشتیبانی شده کش نمیشوند. برای کش کردن یک متد GET، میتوانید از گزینه تنظیمات مسیر مانند export const dynamic = ‘force-static’ در فایل Route Handler خود استفاده کنید.
export const dynamic = 'force-static'
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
},
})
const data = await res.json()
return Response.json({ data })
}
نکته مهم: سایر متدهای HTTP پشتیبانی شده کش نمیشوند، حتی اگر در کنار یک متد GET که کش شده است در همان فایل قرار بگیرند.
مدیریت مسیرهای ویژه (Special Route Handlers)
مدیریت مسیرهای ویژه مانند sitemap.ts، opengraph-image.tsx و icon.tsx به طور پیشفرض ثابت باقی میمانند، مگر اینکه از Dynamic APIs یا تنظیمات پیکربندی پویا استفاده کنند.
حل مسیر (Route Resolution)
شما میتوانید یک مسیر را به عنوان کمترین سطح از اجزای مسیریابی در نظر بگیرید.
مدیریت مسیرها در ناوبریهای سمت کلاینت یا لایهها مانند صفحات مشارکت نمیکنند. همچنین، نمیتوان یک فایل route.js در همان مسیر به عنوان page.js قرار داد.
مثالهای متداول
رفرش دادههای کششده: میتوانید دادههای کششده را با استفاده از Incremental Static Regeneration (ISR) دوباره اعتبارسنجی کنید.
export const revalidate = 60
export async function GET() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return Response.json(posts)
}
کوکیها: میتوانید کوکیها را با استفاده از cookies از next/headers بخوانید یا تنظیم کنید.
import { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = await cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token.value}` },
})
}
هدرها: میتوانید هدرها را با استفاده از headers از next/headers بخوانید.
import { headers } from 'next/headers'
export async function GET(request: Request) {
const headersList = await headers()
const referer = headersList.get('referer')
return new Response('Hello, Next.js!', {
status: 200,
headers: { referer: referer },
})
}
ریدایرکتها: میتوانید با استفاده از redirect به آدرس جدید هدایت کنید.
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
redirect('https://nextjs.org/')
}
مسیرهای پویا (Dynamic Route Segments): مدیریت مسیرها میتواند از segment های پویا برای ایجاد هندلرهای درخواست از دادههای پویا استفاده کند.
export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string }> }
) {
const slug = (await params).slug // 'a', 'b', or 'c'
}
پارامترهای پرسوجو در URL (URL Query Parameters): شیء درخواست (NextRequest) برخی متدهای کمکی اضافی برای راحتتر مدیریت پارامترهای پرسوجو دارد.
import { type NextRequest } from 'next/server'
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
// query is "hello" for /api/search?query=hello
}
استریمینگ (Streaming): استریمینگ به طور معمول در ترکیب با مدلهای زبان بزرگ (LLMs) مانند OpenAI برای تولید محتوای هوش مصنوعی استفاده میشود.
import { openai } from '@ai-sdk/openai'
import { StreamingTextResponse, streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
})
return new StreamingTextResponse(result.toAIStream())
}
بدنه درخواست (Request Body): میتوانید بدنه درخواست را با استفاده از متدهای استاندارد Web API بخوانید.
export async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
}
CORS: میتوانید هدرهای CORS را برای یک مدیریت مسیر خاص تنظیم کنید.
export async function GET(request: Request) {
return new Response('Hello, Next.js!', {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}
وبهوکها (Webhooks): میتوانید از یک Route Handler برای دریافت وبهوکها از سرویسهای ثالث استفاده کنید.
export async function POST(request: Request) {
try {
const text = await request.text()
// پردازش Payload وبهوک
} catch (error) {
return new Response(`Webhook error: ${error.message}`, {
status: 400,
})
}
return new Response('Success!', {
status: 200,
})
}
پاسخهای غیر UI: میتوانید از Route Handlers برای بازگرداندن محتوای غیر UI استفاده کنید. توجه داشته باشید که فایلهای sitemap.xml، robots.txt، آیکونهای اپلیکیشن و تصاویر گراف باز (Open Graph) به طور پیشفرض پشتیبانی میشوند.
export async function GET() {
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Next.js Documentation</title>
<link>https://nextjs.org/docs</link>
<description>The React Framework for the Web</description>
</channel>
</rss>`,
{
headers: {
'Content-Type': 'text/xml',
},
}
)
}
گزینههای پیکربندی بخشها (Segment Config Options): مدیریت مسیرها از همان گزینههای پیکربندی بخشهای مسیر مانند صفحات و لایهها استفاده میکند.
export const dynamic = 'auto' export const dynamicParams = true export const revalidate = false export const fetchCache = 'auto' export const runtime = 'nodejs' export const preferredRegion = 'auto'
مدیریت مسیرها (Route Handlers)
مدیریت مسیرها به شما این امکان را میدهد که برای هر مسیر، هندلرهای درخواست سفارشی با استفاده از Web Request و Response APIs ایجاد کنید.
فایل ویژه Route.js
نکته مهم: مدیریت مسیرها تنها در داخل دایرکتوری app در دسترس است. اینها معادل API Routes در داخل دایرکتوری pages هستند، به این معنی که نیازی به استفاده همزمان از API Routes و Route Handlers نیست.
قرارداد (Convention)
مدیریت مسیرها در یک فایل route.js یا route.ts داخل دایرکتوری app تعریف میشود:
app/api/route.ts
این فایل میتواند از نوع TypeScript باشد:
export async function GET(request: Request) {}
مدیریت مسیرها میتواند در هر جایی از داخل دایرکتوری app تو در تو قرار بگیرد، مشابه فایلهای page.js و layout.js. اما نباید یک فایل route.js در همان سطح segment مسیر به عنوان page.js قرار گیرد.
متدهای پشتیبانی شده HTTP
متدهای HTTP زیر پشتیبانی میشوند: GET، POST، PUT، PATCH، DELETE، HEAD و OPTIONS. اگر متدی که پشتیبانی نمیشود فراخوانی شود، Next.js یک پاسخ 405 Method Not Allowed باز میگرداند.
گسترش APIs NextRequest و NextResponse
علاوه بر پشتیبانی از APIهای بومی Request و Response، Next.js اینها را با NextRequest و NextResponse گسترش داده است تا توابع کمکی مناسب برای استفادههای پیشرفتهتر را فراهم کند.
رفتار (Behavior)
کش (Caching)
مدیریت مسیرها به طور پیشفرض کش نمیشوند. با این حال، میتوانید کش کردن را برای متدهای GET فعال کنید. سایر متدهای HTTP پشتیبانی شده کش نمیشوند. برای کش کردن یک متد GET، میتوانید از گزینه تنظیمات مسیر مانند export const dynamic = ‘force-static’ در فایل Route Handler خود استفاده کنید.
export const dynamic = 'force-static'
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
},
})
const data = await res.json()
return Response.json({ data })
}
نکته مهم: سایر متدهای HTTP پشتیبانی شده کش نمیشوند، حتی اگر در کنار یک متد GET که کش شده است در همان فایل قرار بگیرند.
مدیریت مسیرهای ویژه (Special Route Handlers)
مدیریت مسیرهای ویژه مانند sitemap.ts، opengraph-image.tsx و icon.tsx به طور پیشفرض ثابت باقی میمانند، مگر اینکه از Dynamic APIs یا تنظیمات پیکربندی پویا استفاده کنند.
حل مسیر (Route Resolution)
شما میتوانید یک مسیر را به عنوان کمترین سطح از اجزای مسیریابی در نظر بگیرید.
مدیریت مسیرها در ناوبریهای سمت کلاینت یا لایهها مانند صفحات مشارکت نمیکنند. همچنین، نمیتوان یک فایل route.js در همان مسیر به عنوان page.js قرار داد.
مثالهای متداول
رفرش دادههای کششده: میتوانید دادههای کششده را با استفاده از Incremental Static Regeneration (ISR) دوباره اعتبارسنجی کنید.
export const revalidate = 60
export async function GET() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return Response.json(posts)
}
کوکیها: میتوانید کوکیها را با استفاده از cookies از next/headers بخوانید یا تنظیم کنید.
import { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = await cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token.value}` },
})
}
هدرها: میتوانید هدرها را با استفاده از headers از next/headers بخوانید.
import { headers } from 'next/headers'
export async function GET(request: Request) {
const headersList = await headers()
const referer = headersList.get('referer')
return new Response('Hello, Next.js!', {
status: 200,
headers: { referer: referer },
})
}
ریدایرکتها: میتوانید با استفاده از redirect به آدرس جدید هدایت کنید.
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
redirect('https://nextjs.org/')
}
مسیرهای پویا (Dynamic Route Segments): مدیریت مسیرها میتواند از segment های پویا برای ایجاد هندلرهای درخواست از دادههای پویا استفاده کند.
export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string }> }
) {
const slug = (await params).slug // 'a', 'b', or 'c'
}
پارامترهای پرسوجو در URL (URL Query Parameters): شیء درخواست (NextRequest) برخی متدهای کمکی اضافی برای راحتتر مدیریت پارامترهای پرسوجو دارد.
import { type NextRequest } from 'next/server'
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
// query is "hello" for /api/search?query=hello
}
استریمینگ (Streaming): استریمینگ به طور معمول در ترکیب با مدلهای زبان بزرگ (LLMs) مانند OpenAI برای تولید محتوای هوش مصنوعی استفاده میشود.
import { openai } from '@ai-sdk/openai'
import { StreamingTextResponse, streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
})
return new StreamingTextResponse(result.toAIStream())
}
بدنه درخواست (Request Body): میتوانید بدنه درخواست را با استفاده از متدهای استاندارد Web API بخوانید.
export async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
}
CORS: میتوانید هدرهای CORS را برای یک مدیریت مسیر خاص تنظیم کنید.
export async function GET(request: Request) {
return new Response('Hello, Next.js!', {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}
وبهوکها (Webhooks): میتوانید از یک Route Handler برای دریافت وبهوکها از سرویسهای ثالث استفاده کنید.
export async function POST(request: Request) {
try {
const text = await request.text()
// پردازش Payload وبهوک
} catch (error) {
return new Response(`Webhook error: ${error.message}`, {
status: 400,
})
}
return new Response('Success!', {
status: 200,
})
}
پاسخهای غیر UI: میتوانید از Route Handlers برای بازگرداندن محتوای غیر UI استفاده کنید. توجه داشته باشید که فایلهای sitemap.xml، robots.txt، آیکونهای اپلیکیشن و تصاویر گراف باز (Open Graph) به طور پیشفرض پشتیبانی میشوند.
export async function GET() {
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Next.js Documentation</title>
<link>https://nextjs.org/docs</link>
<description>The React Framework for the Web</description>
</channel>
</rss>`,
{
headers: {
'Content-Type': 'text/xml',
},
}
)
}
گزینههای پیکربندی بخشها (Segment Config Options): مدیریت مسیرها از همان گزینههای پیکربندی بخشهای مسیر مانند صفحات و لایهها استفاده میکند.
export const dynamic = 'auto' export const dynamicParams = true export const revalidate = false export const fetchCache = 'auto' export const runtime = 'nodejs' export const preferredRegion = 'auto'
بینالمللیسازی (Internationalization)
Next.js به شما این امکان را میدهد که مسیریابی و رندر محتوای سایت را بهگونهای پیکربندی کنید که از چندین زبان پشتیبانی کند. این شامل محتوای ترجمهشده (محلیسازی) و مسیرهای بینالمللیشده میشود.
اصطلاحات تخصصی
Locale: شناسهای برای مجموعهای از ترجیحات زبان و قالببندی. معمولاً این شامل زبان مورد نظر کاربر و احتمالاً منطقه جغرافیایی آنها میشود.
en-US: انگلیسی بهکاررفته در ایالات متحده
nl-NL: هلندی بهکاررفته در هلند
nl: هلندی، بدون مشخص کردن منطقه خاص
مرور کلی مسیریابی
بهتر است برای انتخاب locale مورد نظر از ترجیحات زبان کاربر در مرورگر استفاده کنید. تغییر زبان مورد علاقه شما باعث تغییر هدر Accept-Language در درخواست به سمت برنامه میشود.
برای مثال، با استفاده از کتابخانههای زیر، میتوانید درخواست ورودی را بررسی کرده و بر اساس هدرها، localeهایی که قصد پشتیبانی از آنها را دارید و locale پیشفرض، انتخاب کنید:
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
let headers = { 'accept-language': 'en-US,en;q=0.5' }
let languages = new Negotiator({ headers }).languages()
let locales = ['en-US', 'nl-NL', 'nl']
let defaultLocale = 'en-US'
match(languages, locales, defaultLocale) // -> 'en-US'
مسیریابی میتواند بهوسیله زیرمسیر (مثل /fr/products) یا دامنه (مثل my-site.fr/products) بینالمللی شود. با استفاده از این اطلاعات، میتوانید کاربر را بر اساس locale در داخل Middleware هدایت کنید.
import { NextResponse } from "next/server";
let locales = ['en-US', 'nl-NL', 'nl']
function getLocale(request) { ... }
export function middleware(request) {
const { pathname } = request.nextUrl
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (pathnameHasLocale) return
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
return NextResponse.redirect(request.nextUrl)
}
export const config = {
matcher: [
'/((?!_next).*)',
],
}
در نهایت، مطمئن شوید که تمام فایلهای خاص داخل پوشه app/ به صورت تو در تو زیر app/[lang] قرار گیرند. این کار باعث میشود تا روتر Next.js بتواند بهطور دینامیک localeهای مختلف را در مسیر پردازش کرده و پارامتر زبان را به هر لایه و صفحه ارسال کند. بهعنوان مثال:
// app/[lang]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ lang: string }>
}) {
const lang = (await params).lang;
return ...
}
لایه ریشه نیز میتواند در پوشه جدید (مثلاً app/[lang]/layout.js) قرار گیرد.
محلیسازی (Localization)
تغییر محتوای نمایش دادهشده بر اساس locale دلخواه کاربر، یا همان محلیسازی، چیزی نیست که مختص Next.js باشد. الگوهای توضیحدادهشده در ادامه، در هر برنامه وبی به همان شیوه عمل خواهند کرد.
فرض کنید میخواهیم از محتوای انگلیسی و هلندی در اپلیکیشن خود پشتیبانی کنیم. ممکن است دو دیکشنری متفاوت داشته باشیم که اشیایی هستند که از یک کلید به یک رشته محلیشده نگاشت میدهند. بهعنوان مثال:
// dictionaries/en.json
{
"products": {
"cart": "Add to Cart"
}
}
// dictionaries/nl.json
{
"products": {
"cart": "Toevoegen aan Winkelwagen"
}
}
سپس میتوانیم یک تابع getDictionary برای بارگذاری ترجمهها برای locale درخواستی ایجاد کنیم:
// app/[lang]/dictionaries.ts
import 'server-only'
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}
export const getDictionary = async (locale: 'en' | 'nl') =>
dictionaries[locale]()
با توجه به زبان انتخابشده، میتوانیم دیکشنری را در یک لایه یا صفحه بارگذاری کنیم:
// app/[lang]/page.tsx
import { getDictionary } from './dictionaries'
export default async function Page({
params,
}: {
params: Promise<{ lang: 'en' | 'nl' }>
}) {
const lang = (await params).lang
const dict = await getDictionary(lang) // en
return <button>{dict.products.cart}</button> // Add to Cart
}
از آنجا که تمام لایهها و صفحات در پوشه app/ بهطور پیشفرض بهعنوان Server Components عمل میکنند، دیگر نیازی نیست نگران تأثیر حجم فایلهای ترجمه بر اندازه بسته جاوااسکریپت سمت کاربر باشید. این کد تنها در سرور اجرا میشود و فقط HTML حاصل به مرورگر ارسال خواهد شد.
تولید ایستا (Static Generation)
برای تولید مسیرهای ایستا برای یک مجموعه خاص از localeها، میتوانیم از تابع generateStaticParams در هر صفحه یا لایه استفاده کنیم. این میتواند بهصورت جهانی، مثلاً در لایه ریشه، انجام شود:
// app/[lang]/layout.tsx
export async function generateStaticParams() {
return [{ lang: 'en-US' }, { lang: 'de' }]
}
export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode
params: Promise<{ lang: 'en-US' | 'de' }>
}>) {
return (
<html lang={(await params).lang}>
<body>{children}</body>
</html>
)
}
نتیجهگیری
در این مقاله، به بررسی اصول مسیریابی در Next.js پرداختیم و ویژگیهای مختلفی که این فریمورک برای مسیریابی، مدیریت مسیرها و بهینهسازی تجربه کاربری ارائه میدهد را تحلیل کردیم. مسیریابی (Routing) در Next.js به شما این امکان را میدهد که مسیریابی بهینه و انعطافپذیر را با استفاده از الگوها و طرحبندیهای مختلف پیادهسازی کنید.
ویژگیهایی مانند لینکدهی و مسیریابی (Linking and Navigating)، مدیریت خطاها (Error Handling) و رابط کاربری بارگذاری و استریمینگ (Loading UI and Streaming) به شما کمک میکند تا تجربهی کاربری روان و کارآمدی را فراهم کنید، حتی در هنگام بارگذاری دادههای سنگین یا استریمینگ. همچنین، با استفاده از هدایت مجدد (Redirecting) و گروههای مسیر (Route Groups) میتوانید کنترل بیشتری روی رفتار مسیرها و انتقال کاربران به آدرسهای مختلف داشته باشید.
از سوی دیگر، ویژگیهایی مانند مسیرهای داینامیک (Dynamic Routes) و مسیرهای موازی (Parallel Routes) قابلیتهای پیشرفتهای برای ساخت سایتهای پیچیده و سازگار با نیازهای متفاوت فراهم میآورد. ابزارهای مسیریابی در حال ردگیری (Intercepting Routes) و مدیریت مسیرها (Route Handlers) نیز به شما این امکان را میدهند که رفتار مسیرها را بهطور دقیقتری کنترل کنید.
در نهایت، بینالمللیسازی (Internationalization) در Next.js به شما این فرصت را میدهد که سایت خود را برای مخاطبان جهانی بهطور مؤثرتر طراحی و پیادهسازی کنید، تا با پشتیبانی از زبانها و منطقههای مختلف، تجربه کاربری بهتری برای کاربران فراهم شود.
در مجموع، Next.js با ارائهی ابزارهای مختلف برای مسیریابی، مدیریت مسیرها و بینالمللیسازی، به توسعهدهندگان این امکان را میدهد تا وبسایتهایی منعطف، کارآمد و قابل توسعه بسازند که پاسخگوی نیازهای کاربران مختلف در سراسر جهان باشد.
