ملف layout.js
يُستخدم ملف layout
لتحديد تخطيط في تطبيق Next.js الخاص بك.
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
export default function DashboardLayout({ children }) {
return <section>{children}</section>
}
تخطيط الجذر (root layout) هو أعلى تخطيط في دليل app
الجذري. يُستخدم لتحديد علامات <html>
و <body>
وواجهة المستخدم المشتركة عالميًا.
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
المرجع
الخصائص (Props)
children
(مطلوب)
يجب أن تقبل مكونات التخطيط وتستخدم خاصية children
. أثناء التقديم، سيتم ملء children
بمقاطع المسار التي يحيط بها التخطيط. ستكون هذه بشكل أساسي مكون Layout الفرعي (إذا كان موجودًا) أو Page، ولكن يمكن أن تكون أيضًا ملفات خاصة أخرى مثل Loading أو Error عند الاقتضاء.
params
(اختياري)
وعد (Promise) يحل إلى كائن يحتوي على كائن معلمات المسار الديناميكي من المقطع الجذري وصولاً إلى ذلك التخطيط.
export default async function Layout({
params,
}: {
params: Promise<{ team: string }>
}) {
const { team } = await params
}
export default async function Layout({ params }) {
const { team } = await params
}
مثال للمسار | URL | params |
---|---|---|
app/dashboard/[team]/layout.js | /dashboard/1 | Promise<{ team: '1' }> |
app/shop/[tag]/[item]/layout.js | /shop/1/2 | Promise<{ tag: '1', item: '2' }> |
app/blog/[...slug]/layout.js | /blog/1/2 | Promise<{ slug: ['1', '2'] }> |
- نظرًا لأن خاصية
params
هي وعد (Promise)، يجب عليك استخدامasync/await
أو دالةuse
في React للوصول إلى القيم.- في الإصدار 14 وما قبله، كانت
params
خاصية متزامنة (synchronous). لمساعدة في التوافق مع الإصدارات السابقة، لا يزال بإمكانك الوصول إليها بشكل متزامن في Next.js 15، ولكن هذا السلوك سيتم إهماله في المستقبل.
- في الإصدار 14 وما قبله، كانت
تخطيط الجذر (Root Layout)
يجب أن يتضمن دليل app
ملف app/layout.js
جذريًا.
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
- يجب أن يحدد تخطيط الجذر علامات
<html>
و<body>
.- لا يجب إضافة علامات
<head>
يدويًا مثل<title>
و<meta>
إلى تخطيطات الجذر. بدلاً من ذلك، يجب استخدام واجهة برمجة تطبيقات البيانات الوصفية (Metadata API) التي تتعامل تلقائيًا مع المتطلبات المتقدمة مثل البث (streaming) وإزالة التكرار (de-duplicating) لعناصر<head>
.
- لا يجب إضافة علامات
- يمكنك استخدام مجموعات المسار (route groups) لإنشاء تخطيطات جذر متعددة.
- التنقل بين تخطيطات جذر متعددة سيؤدي إلى تحميل صفحة كاملة (بدلاً من التنقل من جانب العميل). على سبيل المثال، التنقل من
/cart
الذي يستخدمapp/(shop)/layout.js
إلى/blog
الذي يستخدمapp/(marketing)/layout.js
سيؤدي إلى تحميل صفحة كاملة. هذا ينطبق فقط على تخطيطات الجذر المتعددة.
- التنقل بين تخطيطات جذر متعددة سيؤدي إلى تحميل صفحة كاملة (بدلاً من التنقل من جانب العميل). على سبيل المثال، التنقل من
محاذير
كائن الطلب (Request Object)
يتم تخزين التخطيطات في ذاكرة التخزين المؤقت للعميل أثناء التنقل لتجنب طلبات الخادم غير الضرورية.
التخطيطات لا يتم إعادة تقديمها. يمكن تخزينها مؤقتًا وإعادة استخدامها لتجنب الحسابات غير الضرورية عند التنقل بين الصفحات. من خلال تقييد التخطيطات من الوصول إلى الطلب الخام، يمكن لـ Next.js منع تنفيذ التعليمات البرمجية للمستخدم التي قد تكون بطيئة أو مكلفة داخل التخطيط، مما قد يؤثر سلبًا على الأداء.
للوصول إلى كائن الطلب، يمكنك استخدام واجهات برمجة التطبيقات headers
و cookies
في مكونات الخادم (Server Components) والوظائف.
import { cookies } from 'next/headers'
export default async function Layout({ children }) {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return '...'
}
import { cookies } from 'next/headers'
export default async function Layout({ children }) {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return '...'
}
معلمات الاستعلام (Query params)
لا يتم إعادة تقديم التخطيطات أثناء التنقل، لذلك لا يمكنها الوصول إلى معلمات البحث التي قد تصبح قديمة.
للوصول إلى معلمات الاستعلام المحدثة، يمكنك استخدام خاصية searchParams
للصفحة، أو قراءتها داخل مكون العميل (Client Component) باستخدام خطاف useSearchParams
. نظرًا لأن مكونات العميل يتم إعادة تقديمها أثناء التنقل، فإن لديها إمكانية الوصول إلى أحدث معلمات الاستعلام.
'use client'
import { useSearchParams } from 'next/navigation'
export default function Search() {
const searchParams = useSearchParams()
const search = searchParams.get('search')
return '...'
}
'use client'
import { useSearchParams } from 'next/navigation'
export default function Search() {
const searchParams = useSearchParams()
const search = searchParams.get('search')
return '...'
}
import Search from '@/app/ui/search'
export default function Layout({ children }) {
return (
<>
<Search />
{children}
</>
)
}
import Search from '@/app/ui/search'
export default function Layout({ children }) {
return (
<>
<Search />
{children}
</>
)
}
مسار الصفحة (Pathname)
لا يتم إعادة تقديم التخطيطات أثناء التنقل، لذلك لا يمكنها الوصول إلى مسار الصفحة الذي قد يصبح قديمًا.
للوصول إلى مسار الصفحة الحالي، يمكنك قراءته داخل مكون العميل (Client Component) باستخدام خطاف usePathname
. نظرًا لأن مكونات العميل يتم إعادة تقديمها أثناء التنقل، فإن لديها إمكانية الوصول إلى أحدث مسار للصفحة.
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
export default function Layout({ children }) {
return (
<>
<Breadcrumbs />
<main>{children}</main>
</>
)
}
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
export default function Layout({ children }) {
return (
<>
<Breadcrumbs />
<main>{children}</main>
</>
)
}
جلب البيانات (Fetching Data)
لا يمكن للتخطيطات تمرير البيانات إلى children
الخاصة بها. ومع ذلك، يمكنك جلب نفس البيانات في مسار أكثر من مرة، واستخدام cache
في React لإزالة التكرار من الطلبات دون التأثير على الأداء.
بدلاً من ذلك، عند استخدام fetch
في Next.js، يتم إزالة تكرار الطلبات تلقائيًا.
export async function getUser(id: string) {
const res = await fetch(`https://.../users/${id}`)
return res.json()
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'
export default async function Layout({ children }) {
const user = await getUser('1')
return (
<>
<nav>
{/* ... */}
<UserName user={user.name} />
</nav>
{children}
</>
)
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'
export default async function Layout({ children }) {
const user = await getUser('1')
return (
<>
<nav>
{/* ... */}
<UserName user={user.name} />
</nav>
{children}
</>
)
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'
export default async function Page() {
const user = await getUser('1')
return (
<div>
<h1>Welcome {user.name}</h1>
</div>
)
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'
export default async function Page() {
const user = await getUser('1')
return (
<div>
<h1>Welcome {user.name}</h1>
</div>
)
}
الوصول إلى المقاطع الفرعية (Accessing child segments)
لا تملك التخطيطات إمكانية الوصول إلى مقاطع المسار الموجودة أسفلها. للوصول إلى جميع مقاطع المسار، يمكنك استخدام useSelectedLayoutSegment
أو useSelectedLayoutSegments
في مكون العميل (Client Component).
import { NavLink } from './nav-link'
import getPosts from './get-posts'
export default async function Layout({
children,
}: {
children: React.ReactNode
}) {
const featuredPosts = await getPosts()
return (
<div>
{featuredPosts.map((post) => (
<div key={post.id}>
<NavLink slug={post.slug}>{post.title}</NavLink>
</div>
))}
<div>{children}</div>
</div>
)
}
import { NavLink } from './nav-link'
import getPosts from './get-posts'
export default async function Layout({ children }) {
const featuredPosts = await getPosts()
return (
<div>
{featuredPosts.map((post) => (
<div key={post.id}>
<NavLink slug={post.slug}>{post.title}</NavLink>
</div>
))}
<div>{children}</div>
</div>
)
}
أمثلة
البيانات الوصفية (Metadata)
يمكنك تعديل عناصر HTML <head>
مثل title
و meta
باستخدام كائن metadata
أو دالة generateMetadata
.
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Layout({ children }: { children: React.ReactNode }) {
return '...'
}
export const metadata = {
title: 'Next.js',
}
export default function Layout({ children }) {
return '...'
}
ملاحظة جيدة: لا يجب إضافة علامات
<head>
يدويًا مثل<title>
و<meta>
إلى تخطيطات الجذر. بدلاً من ذلك، استخدم واجهات برمجة تطبيقات البيانات الوصفية (Metadata APIs) التي تتعامل تلقائيًا مع المتطلبات المتقدمة مثل البث (streaming) وإزالة التكرار (de-duplicating) لعناصر<head>
.
روابط التنقل النشطة (Active Nav Links)
يمكنك استخدام خطاف usePathname
لتحديد ما إذا كان رابط التنقل نشطًا.
نظرًا لأن usePathname
هو خطاف للعميل (Client Hook)، تحتاج إلى استخراج روابط التنقل إلى مكون العميل (Client Component)، والذي يمكن استيراده إلى التخطيط الخاص بك:
import { NavLinks } from '@/app/ui/nav-links'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}
import { NavLinks } from '@/app/ui/nav-links'
export default function Layout({ children }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}
عرض المحتوى بناءً على params
باستخدام شرائح المسارات الديناميكية، يمكنك عرض أو جلب محتوى محدد بناءً على خاصية params
.
export default async function DashboardLayout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ team: string }>
}) {
const { team } = await params
return (
<section>
<header>
<h1>Welcome to {team}'s Dashboard</h1>
</header>
<main>{children}</main>
</section>
)
}
export default async function DashboardLayout({ children, params }) {
const { team } = await params
return (
<section>
<header>
<h1>Welcome to {team}'s Dashboard</h1>
</header>
<main>{children}</main>
</section>
)
}
قراءة params
في مكونات العميل (Client Components)
لاستخدام params
في مكون العميل (والذي لا يمكن أن يكون async
)، يمكنك استخدام دالة use
من React لقراءة الـ promise:
'use client'
import { use } from 'react'
export default function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = use(params)
}
'use client'
import { use } from 'react'
export default function Page({ params }) {
const { slug } = use(params)
}
سجل الإصدارات
الإصدار | التغييرات |
---|---|
v15.0.0-RC | params أصبح الآن promise. يتوفر أداة تحويل الأكواد. |
v13.0.0 | تم تقديم layout . |