ربط إصدارات اللغات البديلة (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'; // المسار لهذه الصفحة
// إنشاء بدائل اللغة
const alternatesMap = locales.reduce((acc, locale) => {
acc[locale] = `${siteBaseUrl}/${locale}${path}`;
return acc;
}, {} as Record<string, string>);
return {
title: 'About Us', // أضف العنوان المترجم الخاص بك
alternates: {
canonical: `${siteBaseUrl}/${lang}${path}`,
languages: {
...alternatesMap,
'x-default': `${siteBaseUrl}/${defaultLocale}${path}`,
},
},
};
}
export default function AboutPage() {
return <div>About page content</div>;
}