ملف 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
}
مثال للمسارURLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>
  • نظرًا لأن خاصية params هي وعد (Promise)، يجب عليك استخدام async/await أو دالة use في React للوصول إلى القيم.
    • في الإصدار 14 وما قبله، كانت params خاصية متزامنة (synchronous). لمساعدة في التوافق مع الإصدارات السابقة، لا يزال بإمكانك الوصول إليها بشكل متزامن في Next.js 15، ولكن هذا السلوك سيتم إهماله في المستقبل.

تخطيط الجذر (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. نظرًا لأن مكونات العميل يتم إعادة تقديمها أثناء التنقل، فإن لديها إمكانية الوصول إلى أحدث مسار للصفحة.

'use client'

import { usePathname } from 'next/navigation'

// منطق مبسط لتتبع المسار
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'

// منطق مبسط لتتبع المسار
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
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).

'use client'

import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function NavLink({
  slug,
  children,
}: {
  slug: string
  children: React.ReactNode
}) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link
      href={`/blog/${slug}`}
      // تغيير النمط اعتمادًا على ما إذا كان الرابط نشطًا
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
'use client'

import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function NavLinks({ slug, children }) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link
      href={`/blog/${slug}`}
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
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>.

يمكنك استخدام خطاف usePathname لتحديد ما إذا كان رابط التنقل نشطًا.

نظرًا لأن usePathname هو خطاف للعميل (Client Hook)، تحتاج إلى استخراج روابط التنقل إلى مكون العميل (Client Component)، والذي يمكن استيراده إلى التخطيط الخاص بك:

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function NavLinks() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        الصفحة الرئيسية
      </Link>

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        حول
      </Link>
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        الصفحة الرئيسية
      </Link>

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        حول
      </Link>
    </nav>
  )
}
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-RCparams أصبح الآن promise. يتوفر أداة تحويل الأكواد.
v13.0.0تم تقديم layout.