البيانات الوصفية (Metadata)

يحتوي Next.js على Metadata API يمكن استخدامه لتعريف البيانات الوصفية لتطبيقك (مثل علامات meta و link داخل عنصر head في HTML) لتحسين تحسين محركات البحث (SEO) وإمكانية المشاركة على الويب.

هناك طريقتان لإضافة بيانات وصفية لتطبيقك:

  • البيانات الوصفية المعتمدة على التكوين: قم بتصدير كائن metadata ثابت أو دالة generateMetadata ديناميكية في ملف layout.js أو page.js.
  • البيانات الوصفية المعتمدة على الملفات: أضف ملفات خاصة ثابتة أو مُنشأة ديناميكيًا إلى مقاطع المسار.

مع هذين الخيارين، سيقوم Next.js تلقائيًا بإنشاء عناصر <head> ذات الصلة لصفحاتك. يمكنك أيضًا إنشاء صور OG ديناميكية باستخدام منشئ ImageResponse.

البيانات الوصفية الثابتة

لتعريف البيانات الوصفية الثابتة، قم بتصدير كائن Metadata من ملف layout.js أو ملف page.js ثابت.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}
export const metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

لجميع الخيارات المتاحة، راجع مرجع API.

البيانات الوصفية الديناميكية

يمكنك استخدام دالة generateMetadata لجلب البيانات الوصفية التي تتطلب قيمًا ديناميكية.

import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // قراءة معلمات المسار
  const id = params.id

  // جلب البيانات
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // الوصول الاختياري إلى البيانات الوصفية الأصلية وتوسيعها (بدلاً من استبدالها)
  const previousImages = (await parent).openGraph?.images || []

  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}

export default function Page({ params, searchParams }: Props) {}
export async function generateMetadata({ params, searchParams }, parent) {
  // قراءة معلمات المسار
  const id = params.id

  // جلب البيانات
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // الوصول الاختياري إلى البيانات الوصفية الأصلية وتوسيعها (بدلاً من استبدالها)
  const previousImages = (await parent).openGraph?.images || []

  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}

export default function Page({ params, searchParams }) {}

لجميع المعلمات المتاحة، راجع مرجع API.

معلومة مفيدة:

  • كل من البيانات الوصفية الثابتة والديناميكية عبر generateMetadata مدعومة فقط في مكونات الخادم (Server Components).
  • طلبات fetch يتم تخزينها مؤقتًا تلقائيًا لنفس البيانات عبر generateMetadata، generateStaticParams، التخطيطات، الصفحات، ومكونات الخادم. يمكن استخدام cache من React إذا كان fetch غير متاح.
  • سينتظر Next.js اكتمال جلب البيانات داخل generateMetadata قبل بث واجهة المستخدم إلى العميل. هذا يضمن أن الجزء الأول من الاستجابة المبثوثة يتضمن علامات <head>.

البيانات الوصفية المعتمدة على الملفات

هذه الملفات الخاصة متاحة للبيانات الوصفية:

يمكنك استخدام هذه الملفات للبيانات الوصفية الثابتة، أو يمكنك إنشاء هذه الملفات برمجيًا باستخدام الكود.

للتنفيذ والأمثلة، راجع مرجع Metadata Files API و إنشاء الصور الديناميكية.

السلوك

البيانات الوصفية المعتمدة على الملفات لها أولوية أعلى وستتجاوز أي بيانات وصفية معتمدة على التكوين.

الحقول الافتراضية

هناك علامتي meta افتراضيتين تتم إضافتهما دائمًا حتى إذا لم يحدد المسار بيانات وصفية:

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

معلومة مفيدة: يمكنك تجاوز علامة viewport الافتراضية.

الترتيب

يتم تقييم البيانات الوصفية بالترتيب، بدءًا من مقطع الجذر وصولاً إلى المقطع الأقرب إلى مقطع page.js النهائي. على سبيل المثال:

  1. app/layout.tsx (تخطيط الجذر)
  2. app/blog/layout.tsx (تخطيط المدونة المتداخل)
  3. app/blog/[slug]/page.tsx (صفحة المدونة)

الدمج

باتباع ترتيب التقييم، يتم دمج كائنات البيانات الوصفية المصدرة من مقاطع متعددة في نفس المسار بشكل سطحي لتشكيل ناتج البيانات الوصفية النهائي للمسار. يتم استبدال المفاتيح المكررة بناءً على ترتيبها.

هذا يعني أن البيانات الوصفية ذات الحقول المتداخلة مثل openGraph و robots التي تم تعريفها في مقطع سابق يتم تجاوزها بواسطة آخر مقطع يقوم بتعريفها.

تجاوز الحقول

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}
app/blog/page.js
export const metadata = {
  title: 'Blog',
  openGraph: {
    title: 'Blog',
  },
}

// الناتج:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

في المثال أعلاه:

  • title من app/layout.js يتم استبداله بـ title في app/blog/page.js.
  • جميع حقول openGraph من app/layout.js يتم استبدالها في app/blog/page.js لأن app/blog/page.js يحدد بيانات openGraph. لاحظ غياب openGraph.description.

إذا كنت ترغب في مشاركة بعض الحقول المتداخلة بين المقاطع أثناء تجاوز البعض الآخر، يمكنك استخراجها إلى متغير منفصل:

app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] }
app/page.js
import { openGraphImage } from './shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'Home',
  },
}
app/about/page.js
import { openGraphImage } from '../shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'About',
  },
}

في المثال أعلاه، يتم مشاركة صورة OG بين app/layout.js و app/about/page.js بينما العناوين مختلفة.

وراثة الحقول

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}
app/about/page.js
export const metadata = {
  title: 'About',
}

// الناتج:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />

ملاحظات

  • title من app/layout.js يتم استبداله بـ title في app/about/page.js.
  • جميع حقول openGraph من app/layout.js يتم وراثتها في app/about/page.js لأن app/about/page.js لا يحدد بيانات openGraph.

إنشاء الصور الديناميكية

يسمح منشئ ImageResponse بإنشاء صور ديناميكية باستخدام JSX و CSS. هذا مفيد لإنشاء صور وسائل التواصل الاجتماعي مثل صور Open Graph وبطاقات Twitter وغيرها.

يستخدم ImageResponse Edge Runtime، ويقوم Next.js تلقائيًا بإضافة العناوين الصحيحة للصور المخزنة مؤقتًا عند الحافة، مما يساعد في تحسين الأداء وتقليل إعادة الحساب.

لاستخدامه، يمكنك استيراد ImageResponse من next/og:

app/about/route.js
import { ImageResponse } from 'next/og'

export const runtime = 'edge'

export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          textAlign: 'center',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  )
}

يتكامل ImageResponse جيدًا مع واجهات Next.js الأخرى، بما في ذلك Route Handlers والبيانات الوصفية المعتمدة على الملفات. على سبيل المثال، يمكنك استخدام ImageResponse في ملف opengraph-image.tsx لإنشاء صور Open Graph أثناء وقت البناء أو ديناميكيًا عند وقت الطلب.

يدعم ImageResponse خصائص CSS الشائعة بما في ذلك flexbox والتحديد المطلق، الخطوط المخصصة، التفاف النص، المركزة، والصور المتداخلة. راجع القائمة الكاملة لخصائص CSS المدعومة.

معلومة مفيدة:

  • تتوفر أمثلة في Vercel OG Playground.
  • يستخدم ImageResponse @vercel/og، Satori، و Resvg لتحويل HTML و CSS إلى PNG.
  • فقط Edge Runtime مدعوم. لن يعمل Node.js الافتراضي.
  • فقط flexbox ومجموعة فرعية من خصائص CSS مدعومة. التخطيطات المتقدمة (مثل display: grid) لن تعمل.
  • الحد الأقصى لحجم الحزمة هو 500KB. يشمل حجم الحزمة JSX، CSS، الخطوط، الصور، وأي أصول أخرى. إذا تجاوزت الحد، فكر في تقليل حجم أي أصول أو جلبها أثناء التشغيل.
  • فقط تنسيقات الخطوط ttf، otf، و woff مدعومة. لتعظيم سرعة تحليل الخط، يُفضل استخدام ttf أو otf بدلاً من woff.

JSON-LD

JSON-LD هو تنسيق للبيانات المنظمة التي يمكن استخدامها من قبل محركات البحث لفهم محتواك. على سبيل المثال، يمكنك استخدامه لوصف شخص، حدث، منظمة، فيلم، كتاب، وصفة، والعديد من أنواع الكيانات الأخرى.

توصيتنا الحالية لـ JSON-LD هي عرض البيانات المنظمة كعلامة <script> في مكونات layout.js أو page.js. على سبيل المثال:

export default async function Page({ params }) {
  const product = await getProduct(params.id)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }

  return (
    <section>
      {/* إضافة JSON-LD إلى صفحتك */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}
export default async function Page({ params }) {
  const product = await getProduct(params.id)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }

  return (
    <section>
      {/* إضافة JSON-LD إلى صفحتك */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}

يمكنك التحقق من صحة بياناتك المنظمة واختبارها باستخدام Rich Results Test من Google أو Schema Markup Validator العام.

يمكنك كتابة JSON-LD باستخدام TypeScript باستخدام حزم المجتمع مثل schema-dts:

import { Product, WithContext } from 'schema-dts'

const jsonLd: WithContext<Product> = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: 'Next.js Sticker',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}