Command Palette

Search for a command to run...

كيفية إضافة Hreflang في Next.js (جهاز توجيه التطبيقات)

كيفية إضافة Hreflang في Next.js (جهاز توجيه التطبيقات)

T
Toolz Team
|Aug 23, 2026|13 دقيقة قراءة

جزء من مجموعة أدوات تحسين محركات البحث

يعمل Toolz على جهاز توجيه التطبيقات Next.js بتسع لغات، وقد مر إعداد hreflang بثلاثة إصدارات خاطئة قبل أن يسير بشكل صحيح. الأول وضع نطاق الوحدة alternates كائن في التخطيط، والذي لا يمكن أن يختلف حسب اللغة، لذلك أعلنت كل صفحة مترجمة عن عنوان URL باللغة الإنجليزية باعتباره عنوانها الأساسي. هذا هو الخطأ الذي يحذف الترجمات: فهو يخبر Google /es/pricing هي نسخة مكررة من /pricing ولا ينبغي فهرستها، مما يؤدي إلى التراجع عن عمل الترجمة بهدوء بينما تظل كل صفحة تعرض بشكل صحيح.

قام الإصدار الثاني بإصلاح النظام الأساسي والإعلان عن كل لغة تم تكوينها كبديل، بما في ذلك اللغات التي لم يتم إنشاء ترجماتها بعد. لذلك نشر الموقع صفحات تسمية مجموعة hreflang التي كانت عبارة عن نص باللغة الإنجليزية تحت عنوان URL باللغة الإسبانية. الإصدار الثالث، الذي يعمل الآن، يستمد المجموعة البديلة من تغطية الترجمة الفعلية، لكل صفحة.

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

ليرة تركية؛DR: في جهاز توجيه التطبيقات، يأتي hreflang من alternates.languages عاد بواسطة generateMetadata، ويجب إنشاء المعيار الأساسي من اللغة النشطة بدلاً من الإعلان عنه مرة واحدة في نطاق الوحدة. قم ببناء كليهما من خريطة محلية واحدة، وقم بإصدار المجموعة المتبادلة الكاملة في كل صفحة في المجموعة، وقم بتضمينها x-default. أعلن فقط عن المناطق التي يوجد محتواها بالفعل، أو قمت بنشر مجموعة تشير إلى صفحات غير مترجمة. ال مولد هريفلانج مفيد للتحقق من المخرجات المقدمة مقابل مجموعة مرجعية تم التحقق من صحتها.

ماذا يعطيك Next.js خارج الصندوق؟

تدعم واجهة برمجة تطبيقات البيانات الوصفية hreflang مباشرة. إرجاع alternates.languages من generateMetadata ينتج <link rel="alternate" hreflang> العلامات في الرأس:

export async function generateMetadata({ params }) {
  const { locale } = await params
  return {
    alternates: {
      canonical: 'https://example.com/de/preise',
      languages: {
        'en-US': 'https://example.com/pricing',
        'de-DE': 'https://example.com/de/preise',
        'x-default': 'https://example.com/pricing',
      },
    },
  }
}

Next.js يعرضها في الرأس ويتعامل مع x-default مفتاح بدون غلاف خاص، كما هو مرجع واجهة برمجة تطبيقات البيانات الوصفية وثائق. ما لا يفعله هو تحديد المواقع التي تنتمي إلى المجموعة، أو الحفاظ على المزامنة الأساسية، أو منعك من وضع الكائن بأكمله في نطاق الوحدة حيث لا يمكن أن يختلف. هذه هي الأجزاء التي عليك القيام بها بشكل صحيح، وهي الأجزاء التي تنكسر.

لاحظ ذلك أيضا metadataBase يؤثر على عناوين URL النسبية هنا. يتطلب Hreflang عناوين URL مطلقة، لذلك يتم تعيين أي منهما metadataBase واستخدم المسارات، أو أنشئ عناوين URL مطلقة بنفسك. أفضّل إنشائها بشكل صريح من أصل تم تكوينه، لأن نشر المعاينة الذي يصدر عناوين URL للإنتاج هو فئة المشكلة الخاصة به.

الخطوة 1: خريطة محلية واحدة، وواحدة فقط

كل شيء في اتجاه مجرى النهر يقرأ من هذا. تحتاج اللغة إلى ثلاث حقائق: مقطع المسار، وعلامة BCP 47 لـ hreflang و <html lang>وما إذا كان يتم الشحن حاليًا.

// i18n/locales.ts
export const DEFAULT_LOCALE = 'en'

export const LOCALES = [
  { code: 'en', hreflang: 'en-US', label: 'English' },
  { code: 'de', hreflang: 'de-DE', label: 'Deutsch' },
  { code: 'fr', hreflang: 'fr-FR', label: 'Français' },
  { code: 'ja', hreflang: 'ja-JP', label: '日本語' },
]

/** '/pricing' -> '/de/pricing', and '/pricing' for the default locale. */
export function localize(path: string, locale: string): string {
  return locale === DEFAULT_LOCALE ? path : `/${locale}${path}`
}

ال code و ال hreflang القيمة هي حقول منفصلة عمدا. مقطع المسار هو de لأنه لا أحد يريد /de-DE/pricing في عناوين URL الخاصة بهم، وقيمة hreflang هي de-DE لأن هذا ما تريد الإعلان عنه. الخلط بينها يعني إما عناوين URL قبيحة أو مجردة de في الشرح، وفي اللحظة التي تضيفها pt-BR بجانب pt-PT يتوقف الخلط عن العمل على الإطلاق.

الخطوة 2: مساعد يقوم ببناء المجموعة

وظيفة واحدة تستخدمها كل صفحة، بحيث لا يمكن للشكل أن ينحرف بين المسارات

export function hreflangAlternates(path: string, locales: string[], base = SITE_URL) {
  const eligible = LOCALES.filter((l) => locales.includes(l.code))
  // A single-entry cluster is not a cluster: emit nothing rather than
  // advertise a one-sided relationship.
  if (eligible.length < 2) return []

  const alts = eligible.map((l) => ({
    hreflang: l.hreflang,
    href: base + localize(path, l.code),
  }))
  alts.push({ hreflang: 'x-default', href: base + localize(path, DEFAULT_LOCALE) })
  return alts
}

قراران هناك يستحقان السرقة.

العودة المبكرة في أقل من مكانين. الصفحة الموجودة باللغة الإنجليزية فقط يجب ألا تصدر أي hreflang على الإطلاق. إن التعليق التوضيحي المرجعي الذاتي الواحد ليس خاطئًا تمامًا، ولكنه ضجيج، وإصدار هذا الرمز الذي أطلقه جعل كل صفحة باللغة الإنجليزية فقط تبدو وكأنها مجموعة مكسورة في تقارير الزحف.

x-default يتم إلحاقه بواسطة المساعد، وليس بواسطة المتصل. كل تطبيق رأيته يترك الاحتياطي للمتصل ينتهي بقالب واحد نسيه. قم بطيها في الشيء الذي يبني المجموعة ولا يمكن نسيانها.

الخطوة 3: قم بتوصيله لإنشاء البيانات الوصفية

// app/[locale]/pricing/page.tsx
import { localize, hreflangAlternates, SHIPPING_LOCALES } from '@/i18n/locales'
import { SITE_URL } from '@/lib/site'

export async function generateMetadata({ params }) {
  const { locale } = await params
  const path = '/pricing'

  const alternates = hreflangAlternates(path, SHIPPING_LOCALES)

  return {
    alternates: {
      // Built from the ACTIVE locale. This is the line that matters.
      canonical: `${SITE_URL}${localize(path, locale)}`,
      ...(alternates.length
        ? { languages: Object.fromEntries(alternates.map((a) => [a.hreflang, a.href])) }
        : {}),
    },
  }
}

الكنسي هو الخط الذي يجب التحديق فيه. يجب أن تكون وظيفة locale، مما يعني أنه لا يمكن أن يعيش في ثابت نطاق الوحدة النمطية، ولا يمكنه العيش في التخطيط الجذري، ولا يمكن مشاركته عبر مقطع اللغة. كل صفحة في المجموعة تنتهي بنفس الشكل languages خريطة وأساسية فريدة من نوعها، وهو الشكل بالضبط هريفلانج والكنسي يتطلب.

نظرًا لأن الخريطة متطابقة عبر المجموعة، يتم تلبية المعاملة بالمثل من الناحية الهيكلية وليس من خلال الانضباط. تسرد الصفحة الألمانية اللغات الألمانية والإنجليزية والفرنسية واليابانية وx-default؛ وكذلك الصفحة الإنجليزية. لا يوجد منطق لكل صفحة يمكن أن يحذف الإدخال.

الخطوة 4: أعلن فقط عما هو موجود

هذه هي الخطوة التي تخطيها معظم الأدلة، وهي الخطوة التي أنتجت أسوأ إصدار من إعدادنا.

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

الإصلاح هو تمرير التغطية الحقيقية بدلاً من التكوين

// Coverage is per-page, not global: this page might be translated
// into three languages while the one next to it has none.
const locales = await localesForPage(path)
const alternates = hreflangAlternates(path, locales)

إقران ذلك مع robots: { index: false, follow: true } على الصفحات الموجودة في لغة ولكن ليس لديها نسخة مترجمة بعد. تظل قابلة للوصول لأي شخص يصل من خلال التنقل الخاص بك، وتظل خارج الفهرس حتى تصل النسخة. كلاهما يسقط تلقائيًا بمجرد ملء التغطية، دون نشر متابعة.

الخطوة 5: متغير خريطة الموقع

إذا كنت تفضل إبقاء hreflang خارج رؤوس الصفحات، فإن نفس المساعد يغذي مسار خريطة الموقع. واحد <loc> لكل جزء من المحتوى مع البدائل كأطفال، بدلاً من واحد <loc> لكل لغة:

// app/sitemap-pages.xml/route.ts
const entries = PAGES.map((path) => {
  const alts = hreflangAlternates(path, SHIPPING_LOCALES)
  const links = alts
    .map((a) => `\n    <xhtml:link rel="alternate" hreflang="${a.hreflang}" href="${a.href}"/>`)
    .join('')
  return `  <url>\n    <loc>${SITE_URL}${path}</loc>${links}\n  </url>`
})

const xml =
  `<?xml version="1.0" encoding="UTF-8"?>\n` +
  `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ` +
  `xmlns:xhtml="http://www.w3.org/1999/xhtml">\n${entries.join('\n')}\n</urlset>`

ال xmlns:xhtml إعلان بشأن <urlset> مطلوب وسهل النسيان؛ وبدونه يتم تجاهل الإدخالات البديلة. لاحظ البنية: قم بإدراج واحد <loc> تقوم كل لغة بضرب الملف بعدد اللغات وتضع كل ترجمة في منافسة مع الترجمة الأساسية الخاصة بها. نفس تغطية الاكتشاف، جزء صغير من الإدخالات.

اختر طريقة واحدة. إذا قمت بإصدار كل من العلامات الرئيسية وإدخالات خريطة الموقع لنفس عناوين URL، فلديك مصدران سوف ينجرفان. علاماتنا الرئيسية للصفحات وبدائل خريطة الموقع لكتالوج الأدوات، وهي مجموعات منفصلة.

هل تحصل اللغة الافتراضية على بادئة URL؟

هذا هو قرار التوجيه الذي يغير كل عنوان URL في مجموعة hreflang الخاصة بك، لذا قم بذلك قبل كتابة المساعد وليس بعده.

هناك استراتيجيتان شائعتان. البادئة حسب الحاجة يخدم اللغة الافتراضية غير مسبوقة في /pricing وكل شيء آخر في /de/pricing. البادئة دائما يخدم كل منطقة تحت شريحة، بما في ذلك /en/pricingمع /pricing إعادة التوجيه. يدعم البرنامج الوسيط Next.js كليهما، ويكشفه next-intl على أنه localePrefix تعيين.

حسب الحاجة (/pricing، /de/pricing) دائما (/en/pricing، /de/pricing)
عناوين URL الإنجليزية الموجودة محفوظ كل واحد يصبح إعادة توجيه
hreflang للغة الافتراضية النقاط على عنوان URL غير البادئ النقاط في /en/...
الهدف الافتراضي X الجذر غير المثبت، وهو أيضًا en عنوان URL يجب اختيار لغة واحدة مسبوقة
التماثل في الكود localize() يحتاج إلى فرع محلي افتراضي لا فرع؛ كل لغة موحدة

أستخدم البادئة حسب الحاجة على موقع موجود، لأن إعادة كتابة كل عنوان URL باللغة الإنجليزية مفهرس للحصول على التماثل يمثل تكلفة كبيرة لتحقيق فوز صغير في الترتيب، كما أن عمليات إعادة التوجيه على صفحاتك ذات حركة المرور الأعلى ليست مجانية. في البناء الجديد، تكون البادئة دائمًا أكثر نظافة: localize يخسر المساعد حالته الخاصة، ولا يوجد سؤال أبدًا حول ما إذا كان /pricing و /en/pricing هي نفس الصفحة.

ما يهم بالنسبة لـ hreflang في كلتا الحالتين هو الاتساق. يجب أن يكون عنوان URL في التعليق التوضيحي، وعنوان URL في الملف الأساسي، وعنوان URL الذي يخدم 200 دون إعادة التوجيه هو نفس السلسلة. المجموعة الوحيدة التي تنكسر بشكل موثوق هي دائمًا البادئة مع استمرار hreflang في الإشارة إلى عناوين URL غير البادئة: كل إدخال للغة الافتراضية يقوم بعد ذلك بتسمية إعادة توجيه، وتعيش علامة الإرجاع على الوجهة بدلاً من عنوان URL الذي قمت بتسميته.

ما هي المكتبة التي يجب عليك استخدامها؟

تنتهي معظم إعدادات App Router i18n next-intl أو next-i18next، ولا يقوم أي منهما بإنشاء hreflang لك. إنهم يحلون التوجيه وتحميل الرسائل؛ لا تزال التعليقات التوضيحية ملكك لإصدارها. لا بأس، لأن المساعد أعلاه يتكون من عشرين سطرًا وتريده تحت سيطرتك على أي حال.

قلق يتم التعامل معها من قبل المكتبة لك
التوجيه المحلي والبرامج الوسيطة نعم
كتالوجات الرسائل والاحتياطية نعم
<html lang> عادة التحقق من مطابقتها لقيمة hreflang
alternates.canonical لكل لغة لا البناء من اللغة النشطة
alternates.languages لا بناء من الخريطة المحلية
x-default لا إلحاق في المساعد

الشيء الوحيد الخاص بالمكتبة الذي يجب التحقق منه هو <html lang>. البيانات المنظمة وhreflang التي تتعارض مع لغة المستند أسوأ من لا شيء، والتخطيط الذي يقوم بالأكواد الصلبة lang="en" أثناء خدمة اللغة الألمانية، هناك بقايا شائعة بشكل مدهش.

كيف يمكنك التحقق من ذلك؟

قم ببناء وقراءة المخرجات الفعلية، لأن القالب ليس هو الدليل

next build && next start
curl -s http://localhost:3000/de/preise | grep -E 'rel="(canonical|alternate)"'

ثلاثة فحوصات على هذا الإخراج. الأساسي هو عنوان URL الذي جلبته. يوجد بالضبط إدخال واحد لكل لغة بالإضافة إلى إدخال واحد x-default. كل href مطلق ويتطابق مع عنوان URL الذي يتم تقديم الصفحة عليه، مع تضمين الشرطة المائلة اللاحقة.

ثم أحضر أخًا واختلط بين الاثنين languages كتل. يجب أن تكون متطابقة بالبايت؛ فقط القانوني يختلف. إذا لم تكن متطابقة، فإن شيئًا ما في صفحتك هو بناء المجموعة من اللغة الحالية وليس من المجموعة، وهو فشل المعاملة بالمثل في تمويهها الأكثر شيوعًا.

وأخيرا، لصق المجموعة في مولد علامة Hreflang للتحقق من صحة الرموز نفسها. فهو يتحقق من كل قيمة مقابل شكل ISO، ويضع علامات فرعية غير معروفة، ويلتقط التكرارات، ويحذر عندما لا تحتوي المجموعة على أي احتياطي. يتم تشغيله من جانب العميل، لذلك تظل بنية URL المرحلية خاصة. ال دليل الأداة يمشي من خلال سير العمل، و 12 خطأ شائع في hreflang يغطي ما يجب البحث عنه بمجرد بدء الترميز.

الأسئلة المتداولة

كيف يمكنني إضافة علامات hreflang في جهاز توجيه تطبيق Next.js؟

العودة alternates.languages كائن من generateMetadata، وتعيين كل علامة BCP 47 إلى عنوان URL المطلق الخاص بها، وتعيينها alternates.canonical من اللغة النشطة. Next.js يعرض كلاهما في الرأس.

لماذا يجب بناء المعيار داخل إنشاء البيانات الوصفية؟

لأنه يجب أن يختلف حسب اللغة. لا يمكن استخدام النطاق الأساسي للوحدة النمطية، لذلك ستعلن كل صفحة مترجمة أن عنوان URL الافتراضي باللغة المحلية هو عنوان أساسي، مما يزيل الترجمة من الفهرس.

هل يقوم intl التالي بإنشاء علامات hreflang تلقائيًا؟

رقم next-intl يتعامل مع التوجيه وتحميل الرسائل. لا تزال التعليقات التوضيحية hreflang والأساسية لكل لغة هي ملكك لتبث منها generateMetadata.

كيف يمكنني إضافة x-default في Next.js؟

أضف 'x-default' مفتاح alternates.languages كائن يشير إلى عنوان URL الاحتياطي الخاص بك. يقوم Next.js بتمرير المفتاح دون معالجة خاصة. قم بإلحاقه داخل المساعد الذي يقوم بإنشاء المجموعة حتى لا يتمكن أي قالب من نسيانها.

هل يجب أن يكون hreflang في الرأس أو خريطة الموقع في Next.js؟

يعمل أي منهما، ولكن ليس كلاهما لنفس عناوين URL. علامات الرأس عبر generateMetadata أسهل في التصحيح. يناسب مسار خريطة الموقع الكتالوجات الكبيرة ويبقي رؤوس الصفحات هزيلة؛ تذكر xmlns:xhtml إعلان بشأن <urlset>.

ماذا لو تمت ترجمة اللغة جزئيًا فقط؟

أعلن فقط عن المواقع التي يوجد محتواها بالفعل لتلك الصفحة، وقم بوضع علامة على الصفحات غير المترجمة noindex, follow. الإعلان عن القائمة المحلية التي تم تكوينها ينشر مجموعة تشير إلى الصفحات غير المترجمة.

هل يجب أن تكون عناوين URL الخاصة بـ hreflang في Next.js مطلقة؟

نعم. يتطلب Hreflang عناوين URL مؤهلة بالكامل. إما تعيين metadataBase أو قم بإنشاء عناوين URL من أصل تم تكوينه بحيث لا تؤدي عمليات نشر المعاينة إلى إصدار عناوين URL للإنتاج.

كيف يمكنني التحقق من إخراج hreflang المعروض؟

قم بتشغيل إصدار الإنتاج، وأحضر صفحتين شقيقتين، وقارن. ال languages يجب أن تكون الكتل متطابقة مع البايت عبر المجموعة بينما يشير كل منها إلى عنوان URL الخاص به.


Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!