ربط إصدارات اللغات البديلة (hreflang)

إخبار محركات البحث عن صفحاتك المترجمة

المشكلة

يحتوي التطبيق على محتوى متطابق متاح على /en/page و /fr/page. تعتبر محركات البحث هاتين صفحتين منفصلتين ومتنافستين. بدون آلية لربطهما، يتم تقسيم تصنيفات البحث، وقد يتم عرض الصفحة الإنجليزية للمستخدمين في فرنسا في نتائج البحث بدلاً من الصفحة الفرنسية.

الحل

استخدم خاصية alternates داخل دالة generateMetadata في Next.js. من خلال توفير قائمة بجميع اللغات المتاحة لصفحة معينة، سيقوم Next.js تلقائياً بإنشاء وسوم <link rel="alternate" hreflang="..." /> في <head> المستند، مما يشير إلى العلاقة بين هذه الصفحات لمحركات البحث.

الخطوات

1. حدد عنوان URL الأساسي لموقعك

تتطلب وسوم hreflang عناوين URL مطلقة وليست نسبية. قم بتخزين عنوان URL الأساسي القانوني لموقعك في ملف التكوين.

// i18n-config.ts

export const locales = ['en', 'es', 'fr'];
export const defaultLocale = 'en';
export const siteBaseUrl = 'https://www.example.com'; // Your production URL

2. أضف alternates إلى generateMetadata

في ملف app/[lang]/layout.tsx الخاص بك (للتطبيق على جميع الصفحات) أو في ملف صفحة محدد، قم بتصدير دالة generateMetadata.

// app/[lang]/layout.tsx
import { locales, siteBaseUrl } from '@/i18n-config';
import type { Metadata } from 'next';

type Props = {
  params: { lang: string };
  children: React.ReactNode;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params;

  // Create language alternates
  const alternatesMap = locales.reduce((acc, locale) => {
    acc[locale] = `${siteBaseUrl}/${locale}`;
    return acc;
  }, {} as Record<string, string>);

  return {
    alternates: {
      canonical: `${siteBaseUrl}/${lang}`,
      languages: {
        ...alternatesMap,
        'x-default': `${siteBaseUrl}/${defaultLocale}`,
      },
    },
  };
}

// Rest of your layout component
export default function RootLayout({ children, params }: Props) {
  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  );
}

ينشئ هذا الكود كائن languages الذي يربط كل لغة بعنوان URL الأساسي المطلق الخاص بها (على سبيل المثال، en: 'https://www.example.com/en'). كما يحدد عنوان URL لـ canonical للصفحة الحالية وعنوان URL لـ x-default، والذي يخبر محركات البحث بالإصدار الذي يجب عرضه للمستخدمين في اللغات غير المحددة.

3. تعامل مع alternates في الصفحات المتداخلة

بالنسبة للصفحات المتداخلة مثل /about، يجب عليك التأكد من أن دالة البيانات الوصفية تتضمن المسار الكامل.

// app/[lang]/about/page.tsx
import { locales, siteBaseUrl, defaultLocale } from '@/i18n-config';
import type { Metadata } from 'next';

type Props = {
  params: { lang: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params;
  const path = '/about'; // The path for this page

  // Create language alternates
  const alternatesMap = locales.reduce((acc, locale) => {
    acc[locale] = `${siteBaseUrl}/${locale}${path}`;
    return acc;
  }, {} as Record<string, string>);

  return {
    title: 'About Us', // Add your translated title
    alternates: {
      canonical: `${siteBaseUrl}/${lang}${path}`,
      languages: {
        ...alternatesMap,
        'x-default': `${siteBaseUrl}/${defaultLocale}${path}`,
      },
    },
  };
}

export default function AboutPage() {
  return <div>About page content</div>;
}