Next.js i18n على نطاق واسع: 30 لغة، 91 ألف صفحة، Vercel ISR
Next.js i18n في النطاق الكبير: 30 لغة، 91 ألف صفحة، Vercel ISR
في العام الماضي، أطلقنا مشروع Next.js الذي يجعلني عصبياً قليلاً عندما أفكر فيه. ثلاثون لغة. أكثر من 91,000 صفحة يتم إنشاؤها بشكل ثابت. Vercel ISR يحافظ على كل شيء محدثاً. هذا نوع من المشاريع حيث يعني قرار معماري واحد خاطئ أنك تحدق في بنايات مدة 4 ساعات، فواتير استضافة بقيمة 800 دولار/الشهر، أو -- في أسوأ الحالات -- موقع لا يعمل بشكل صحيح في الكورية.
هذه هي قصة كيف حصلنا على النتيجة الصحيحة (والأجزاء التي لم نحصل عليها بشكل صحيح في البداية). إذا كنت تبني تطبيق Next.js متعدد اللغات واسع النطاق وتتساءل ما إذا كان ISR يمكنه فعلاً التعامل معه في الإنتاج، فهذا المقال مخصص لك.

جدول المحتويات
- المشكلة: لماذا 91 ألف صفحة مختلفة
- قرارات العمارة التي اتخذناها مبكراً
- إعداد Next.js i18n لـ 30 لغة
- استراتيجية ISR التي نجحت فعلاً
- خط أنابيب المحتوى وتكامل نظام CMS بدون رأس
- نتائج الأداء و Core Web Vitals
- تفصيل التكاليف على Vercel
- الأخطاء التي ارتكبناها وكيفية إصلاحها
- متى تستخدم هذه المجموعة (ومتى لا)
- الأسئلة الشائعة
المشكلة: لماذا 91 ألف صفحة مختلفة
دعني أشرح الوضع. كان العميل عبارة عن علامة تجارية للتجارة الإلكترونية على مستوى المؤسسة تتوسع إلى 30 سوق. احتاج كل سوق إلى:
- صفحات منتجات محلية (~2,800 منتج × 30 لغة = 84,000 صفحة)
- صفحات فئات (~120 فئة × 30 لغة = 3,600 صفحة)
- صفحات تسويقية يدفعها نظام CMS (~120 × 30 = 3,600 صفحة)
- المجموع: حوالي 91,200 عنوان URL فريد
مع getStaticPaths عادي والإنشاء الثابت الكامل، كان الإنشاء الأولي سيستغرق ما بين 3 و 5 ساعات. هذا ليس خطأ كتابي. قمنا بقياس النماذج الأولية المبكرة وشاهدنا الرقم يرتفع. كل نشر سيعني ساعات من خطر التوقف، وكانت فريق المحتوى تريد نشر التحديثات عدة مرات في اليوم.
SSR لم تكن خياراً أيضاً. أظهرت أنماط حركة المرور للعميل طفرات ضخمة أثناء فعاليات البيع -- نحن نتحدث عن 50 ألف مستخدم متزامن. عرض صفحات تم عرضها على الخادم من 91 ألف متغير صفحة محتمل تحت هذا الحمل يتطلب حسابات جادة ويقدم الكمون الذي يقتل معدلات التحويل.
ISR كانت الإجابة. لكن ISR في هذا المقياس لها مجموعة خاصة بها من التحديات التي لا توثيق Next.js حقاً يعدك لها.
قرارات العمارة التي اتخذناها مبكراً
قبل كتابة سطر واحد من كود i18n، اتخذنا ثلاثة قرارات معمارية وفّرت لنا شهوراً من الألم لاحقاً.
القرار 1: توجيه المسار الفرعي وليس المجالات
يدعم Next.js استراتيجيتي i18n: توجيه المسار الفرعي (/fr/products/...) وتوجيه المجال (fr.example.com). اخترنا توجيه المسار الفرعي. إليك السبب:
| العامل | توجيه المسار الفرعي | توجيه المجال |
|---|---|---|
| تعقيد DNS/SSL | مجال واحد | 30 مجال/نطاق فرعي للإدارة |
| نشر Vercel | مشروع واحد | مشروع واحد (لكن نفقات إعدادات المجال) |
| حقوق ربط تحسين محركات البحث | موحد على مجال واحد | مقسم عبر المجالات |
| كفاءة ذاكرة تخزين مؤقت CDN | أفضل (ذاكرة تخزين مؤقت حافة مشتركة) | مجزأة |
| إعداد التحليلات | أبسط | 30 خاصية أو تصفية معقدة |
لمعظم المشاريع التي تحت 50 لغة، توجيه المسار الفرعي هو الخيار الأفضل. توجيه المجال منطقي عندما تحتاج إلى نطاقات محددة حسب البلد لأسباب قانونية أو عندما يكون لديك أسواقك معمارية محتوى بشكل جذري مختلفة.
القرار 2: next-intl أكثر من next-i18next
قيّمنا كلتا المكتبتين بشكل مكثف. في 2025، next-intl (v4.x) أصبحت الخيار الأقوى لمشاريع App Router، على الرغم من أننا كنا على Pages Router لهذا الإنشاء. حتى على Pages Router، أعطتنا next-intl:
- دعم TypeScript أفضل مع مفاتيح رسالة آمنة النوع
- حزمة عميل أصغر (حوالي 2.1 كيلوبايت مضغوطة مقابل ~5 كيلوبايت لـ next-i18next)
- دعم أصلي لتنسيق رسالة ICU (الجموع، الجنس، تنسيق الأرقام)
- إعداد أبسط لصفحات ISR
القرار 3: الإنشاء الثابت الجزئي + ISR
هذا كان الكبير. بدلاً من محاولة إنشاء جميع الصفحات الـ 91 ألف بشكل ثابت في وقت الإنشاء، قمنا بـ pre-building فقط الصفحات عالية حركة المرور (حوالي 8,000) وتركنا ISR تتعامل مع الباقي حسب الطلب.
// pages/[locale]/products/[slug].tsx
export async function getStaticPaths() {
// فقط pre-generate أفضل 100 منتج × أفضل 5 لغات
const topProducts = await getTopProducts(100);
const primaryLocales = ['en', 'de', 'fr', 'es', 'ja'];
const paths = topProducts.flatMap(product =>
primaryLocales.map(locale => ({
params: { slug: product.slug, locale },
}))
);
return {
paths,
fallback: 'blocking', // ISR تتعامل مع كل شيء آخر
};
}
هذا جلب وقت الإنشاء من 3+ ساعات إلى حوالي 12 دقيقة. الصفحات المتبقية 83,000 يتم إنشاؤها في الطلب الأول وتخزينها مؤقتاً في الحافة.

إعداد Next.js i18n لـ 30 لغة
إعداد i18n المدمج في Next.js في next.config.js يتعامل مع اكتشاف المنطقة والتوجيه. إليك شكل التكوين الخاص بنا (مختصر):
// next.config.js
const nextConfig = {
i18n: {
locales: [
'en', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'pl', 'cs', 'da',
'sv', 'fi', 'nb', 'hu', 'ro', 'bg', 'hr', 'sk', 'sl', 'el',
'tr', 'ja', 'ko', 'zh-CN', 'zh-TW', 'th', 'vi', 'id', 'ms', 'ar'
],
defaultLocale: 'en',
localeDetection: false, // نتعامل مع هذا بأنفسنا
},
};
هناك بضعة أشياء يجب ملاحظتها هنا. قمنا بتعطيل localeDetection لأن الاكتشاف المدمج (استناداً إلى رؤوس Accept-Language) كان يسبب مشاكل في ذاكرة تخزين مؤقت ISR. عندما يخزن CDN من Vercel صفحة مؤقتاً، تحتاج المنطقة إلى أن تكون حتمية من عنوان URL، وليس من الرؤوس. السماح لـ Next.js بإعادة التوجيه التلقائي بناءً على لغة المتصفح يعني عدم الضرب في الذاكرة المؤقتة والسلوك غير المتسق.
بدلاً من ذلك، بنينا برنامج middleware اكتشاف منطقة مخصص يعمل في مسار الجذر فقط:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const SUPPORTED_LOCALES = ['en', 'de', 'fr', /* ... */];
const DEFAULT_LOCALE = 'en';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// إعادة التوجيه فقط في مسار الجذر
if (pathname === '/') {
const acceptLanguage = request.headers.get('accept-language') || '';
const preferred = acceptLanguage.split(',')[0]?.split('-')[0] || DEFAULT_LOCALE;
const locale = SUPPORTED_LOCALES.includes(preferred) ? preferred : DEFAULT_LOCALE;
return NextResponse.redirect(new URL(`/${locale}`, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/'],
};
هيكل ملف الترجمة
مع 30 لغة، إدارة ملف الترجمة تصبح مصدر قلق حقيقي. نظمنا الترجمات حسب الفضاء:
messages/
├── en/
│ ├── common.json
│ ├── product.json
│ ├── checkout.json
│ └── marketing.json
├── de/
│ ├── common.json
│ ├── product.json
│ └── ...
└── ar/
└── ...
إجمالي حمولة الترجمة عبر جميع اللغات حوالي 4.2 ميجابايت. لكن لأننا نحمل الترجمات لكل صفحة باستخدام getStaticProps، تحمل كل صفحة فردية فقط 15-40 كيلوبايت من بيانات الترجمة لمنطقتها والفضاء الخاص بها. هذا حاسم -- أنت لا تريد شحن جميع 30 لغة إلى العميل.
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: {
...(await import(`../../messages/${locale}/common.json`)).default,
...(await import(`../../messages/${locale}/product.json`)).default,
},
},
revalidate: 300, // ISR: إعادة التحقق كل 5 دقائق
};
}
دعم RTL للعربية
العربية كانت اللغة الوحيدة RTL في مجموعتنا. تعاملنا معها باستخدام غلاف تخطيط بسيط:
const direction = locale === 'ar' ? 'rtl' : 'ltr';
return (
<html lang={locale} dir={direction}>
<body className={direction === 'rtl' ? 'font-arabic' : 'font-sans'}>
{children}
</body>
</html>
);
بالإضافة إلى متغير rtl: من Tailwind لتعديلات التباعد والتخطيط. هذا عمل بشكل جيد بشكل مفاجئ -- ربما 5% من CSS الخاص بنا احتاج إلى تجاوزات خاصة بـ RTL.
استراتيجية ISR التي نجحت فعلاً
ISR (الإنشاء الثابت الإضافي) هو البطل في هذه القصة، لكن استخدامه بشكل جيد في النطاق يتطلب فهم كيفية عمل بنية Vercel الأساسية فعلاً.
توقيت إعادة التحقق
استخدمنا فترات إعادة تحقق مختلفة حسب نوع المحتوى:
| نوع الصفحة | فترة إعادة التحقق | المنطق |
|---|---|---|
| صفحات المنتج | 300 ثانية (5 دقائق) | تتغير الأسعار/المخزون بشكل متكرر |
| صفحات الفئات | 900 ثانية (15 دقيقة) | قوائم المنتجات تتحدث بشكل أقل |
| صفحات التسويق/CMS | 3600 ثانية (1 ساعة) | تغييرات المحتوى مخطط لها |
| الصفحة الرئيسية لكل منطقة | 600 ثانية (10 دقائق) | توازن بين الحداثة والذاكرة المؤقتة |
إعادة التحقق عند الطلب
للتحديثات الحرجة (تغييرات الأسعار، نقص المخزون)، قمنا بإعداد إعادة تحقق عند الطلب عبر webhook من نظام CMS بدون رأس:
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { secret, slug, locales } = req.body;
if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: 'Invalid secret' });
}
try {
const targetLocales = locales || ['en']; // الافتراضي للغة الإنجليزية إذا لم يتم تحديدها
const revalidations = targetLocales.map((locale: string) =>
res.revalidate(`/${locale}/products/${slug}`)
);
await Promise.all(revalidations);
return res.json({ revalidated: true, paths: targetLocales.length });
} catch (err) {
return res.status(500).json({ message: 'Error revalidating' });
}
}
إحدى المشاكل: عندما تقوم بإعادة تحقق من منتج موجود في 30 لغة، تقوم بإجراء 30 مكالمة إعادة تحقق. للتحديث الجماعي لـ 100 منتج، هذا 3000 طلب إعادة تحقق. كان علينا إضافة حد للمعدل والتوصل إلى قائمة الانتظار هذه من خلال دالة serverless لتجنب الوصول إلى حدود API الخاصة بـ Vercel.
نمط Stale-While-Revalidate
جمال ISR هو أنها تخدم المحتوى العائد مع إعادة الإنشاء في الخلفية. بالنسبة لهذا المشروع، كان يعني أن المستخدمين دائماً يحصلون على استجابة سريعة (HTML مخزن مؤقتاً من حافة Vercel)، حتى لو كانت البيانات قديمة حتى 5 دقائق. لموقع تجارة إلكترونية، كان هذا مقايضة مقبولة -- تدفق السلة والدفع دائماً يضرب API مباشرة للمخزون/تسعير في الوقت الفعلي.
خط أنابيب المحتوى وتكامل نظام CMS بدون رأس
المحتوى يعيش في نظام CMS بدون رأس (Contentful، في هذه الحالة، على الرغم من أننا قمنا بإعدادات مماثلة مع Sanity و Storyblok للعملاء الآخرين -- انظر خدمات تطوير نظام CMS بدون رأس للمزيد من هذا).
نموذج تحديث Contentful كان يعمل بشكل جيد لـ 30 لغة. كل إدخال له قيم حقول خاصة بمنطقة، و API الخاص بهم يدعم الاستعلام حسب المنطقة. لكن هناك اعتبار الأداء: جلب منتج مع جميع بيانات 30 لغة أكبر بكثير من جلب لغة واحدة.
كنا نسأل دائماً عن منطقة واحدة في getStaticProps:
const product = await contentfulClient.getEntry(productId, {
locale: mapToContentfulLocale(locale), // 'en-US', 'de-DE', إلخ.
include: 2, // حل 2 مستوى من الإدخالات المرتبطة
});
أبقى هذا أوقات استجابة API تحت 200ms حتى لإدخالات المنتجات المعقدة ذات المراجع المتعددة.
إدارة الترجمة
لترجمات UI (الأزرار، التسميات، رسائل الخطأ)، استخدمنا Crowdin المدمجة مع مستودع Git الخاص بنا. سير العمل:
- يضيف المطورون سلاسل إنجليزية جديدة إلى
messages/en/*.json - Crowdin يزامن ويخطر المترجمين
- الترجمات تعود كـ PRs
- CI يتحقق من بنية JSON والاكتمال
- الترجمات المفقودة ترجع إلى الإنجليزية
استراتيجية الإرجاع حاسمة. أنت لا تريد أبداً صفحة إنتاج تعرض مفاتيح ترجمة مثل product.add_to_cart. سلسلة الإرجاع لدينا كانت: المنطقة المطلوبة → عائلة اللغة (مثل pt-BR → pt) → الإنجليزية.
نتائج الأداء و Core Web Vitals
بعد الإطلاق، إليك ما قسناه عبر جميع 30 لغة:
| المقياس | الهدف | الفعلي (P75) | ملاحظات |
|---|---|---|---|
| LCP | < 2.5 ثانية | 1.8 ثانية | ضربة ذاكرة تخزين مؤقت ISR |
| FID | < 100 ميلي ثانية | 45 ميلي ثانية | JavaScript جانب العميل الحد الأدنى |
| CLS | < 0.1 | 0.03 | استراتيجية تحميل الخط ساعدت |
| TTFB | < 800 ميلي ثانية | 120 ميلي ثانية | حافة Vercel، صفحات مخزنة مؤقتاً |
| TTFB (عدم وجود ذاكرة تخزين مؤقت) | < 2 ثانية | 1.4 ثانية | وقت إنشاء ISR |
| وقت الإنشاء | < 20 دقيقة | 11 دقيقة 40 ثانية | pre-generating فقط 8 كيلو صفحة |
أرقام TTFB هي النجم هنا. 120 ميلي ثانية لصفحات مخزنة مؤقتاً يعني أن المستخدمين في طوكيو وساو باولو وفرانكفورت جميعاً يحصلون على استجابات سريعة من عقد حافة قريبة. 1.4 ثانية للأخطاء في الذاكرة المؤقتة هو وقت إنشاء ISR -- مقبول لأنه يحدث مرة واحدة فقط لكل صفحة لكل فترة إعادة التحقق.
تحميل الخط لـ 30 لغة
أحد التحديات المتعلقة بالأداء الخاصة بالمواقع متعددة اللغات: الخطوط. لا يمكنك استخدام عائلة خط واحدة لـ 30 لغة. احتجنا:
- لاتيني/سيريلي: Inter (معظم اللغات الأوروبية)
- عربي: Noto Sans Arabic
- CJK: Noto Sans JP/KR/SC/TC
- تايلندي: Noto Sans Thai
استخدام next/font مع تحميل خط لكل منطقة منع تنزيلات الخط غير الضرورية. يحصل مستخدم يزور الموقع الياباني فقط على Noto Sans JP فقط، ليس الخطوط العربية أو التايلاندية.
تفصيل التكاليف على Vercel
دعنا نتحدث عن المال، لأن هذا هو المكان الذي يصبح فيه ISR واسع النطاق مثيراً للاهتمام. إليك تفصيل فاتورة Vercel الشهرية في 2025:
| عنصر السطر | التكلفة الشهرية | ملاحظات |
|---|---|---|
| خطة Vercel Pro | 20 دولار/مقعد × 4 | خطة الفريق الأساسية |
| النطاق الترددي (8 تيرابايت/شهر) | ~320 دولار | 40 دولار/تيرابايت بعد أول 1 تيرابايت |
| تنفيذ وظائف Serverless | ~180 دولار | إعادة إنشاء ISR + مسارات API |
| تنفيذات Edge Middleware | ~45 دولار | اكتشاف المنطقة |
| كتابات ISR | ~90 دولار | عمليات كتابة الذاكرة المؤقتة |
| الإجمالي | ~715 دولار/شهر |
لموقع يتعامل مع 2M+ views/month عبر 30 لغة، 715 دولار معقول جداً. الخيار البديل -- تشغيل SSR على البنية الأساسية المخصصة -- كان سيكلف 2,000-4,000 دولار/شهر للأداء والموثوقية المكافئة.
شيء واحد يجب مراقبته: تكاليف كتابة ذاكرة تخزين مؤقت ISR يمكن أن ترتفع إذا قمت بتشغيل إعادة تحقق جماعية. كان لدينا حادثة حيث نشر CMS جماعي أطلق إعادة تحقق لـ 15,000 صفحة في نفس الوقت. كان هذا الحدث وحده يكلف حوالي 40 دولار من تنفيذات الدوال الإضافية. نحن الآن نجمع استدعاءات إعادة التحقق مع تأخير 100 ميلي ثانية بينهما.
الأخطاء التي ارتكبناها وكيفية إصلاحها
سأكون كاذباً إذا قلت إن هذا سار بسلاسة من اليوم الأول. هنا أكبر الأخطاء:
الخطأ 1: إنشاء جميع اللغات في وقت الإنشاء
حاولت نهجنا الأول إنشاء كل صفحة بكل لغة. جرى الإنشاء لمدة 3 ساعات و 47 دقيقة. ثم فشل لأن مهلة الإنشاء الخاصة بـ Vercel (على Pro) هي 45 دقيقة. حتى بعد الانتقال إلى خادم إنشاء مخصص، كانت عملية النشر مروعة.
الإصلاح: إنشاء جزئي مع fallback: 'blocking'. بناء فقط الصفحات التي تهم أكثر، دع ISR تتعامل مع الذيل الطويل.
الخطأ 2: عدم تعيين `fallback` بشكل صحيح
استخدمنا في البداية fallback: true بدلاً من fallback: 'blocking'. الفرق مهم: true يخدم حالة هيكل عظمي/تحميل في الطلب الأول، بينما blocking ينتظر إنشاء الصفحة. مع true، كنا نحصل على أخطاء الترطيب لأن مكونات المنتج لدينا توقعت بيانات لم تكن موجودة بعد أثناء عرض الإرجاع.
الإصلاح: تحويل إلى fallback: 'blocking'. زائر أول لصفحة غير مخزنة مؤقتاً ينتظر 1-2 ثانية، لكن كل شخص بعد ذلك يحصل على النسخة المخزنة مؤقتاً على الفور.
الخطأ 3: كانت علامات Hreflang الخاصة بـ SEO خاطئة
هذا واحد سهل للفساد. Google يحتاج إلى علامات hreflang لفهم العلاقة بين الصفحات المحلية. كان التنفيذ الأولي لدينا يفتقد إلى علامة x-default وكان لديه تناقضات بين علامات <link> و خريطة الموقع XML.
// تنفيذ hreflang الصحيح
<Head>
{locales.map(loc => (
<link
key={loc}
rel="alternate"
hrefLang={loc}
href={`https://example.com/${loc}${path}`}
/>
))}
<link rel="alternate" hrefLang="x-default" href={`https://example.com/en${path}`} />
</Head>
الخطأ 4: إنشاء خريطة الموقع
مع 91 ألف عنوان URL، ملف خريطة موقع XML واحد لن يعمل (حد Google هو 50,000 URL لكل خريطة موقع). احتجنا إلى فهرس خريطة موقع مع خرائط موقع فرعية متعددة، مقسم حسب المنطقة:
<!-- sitemap-index.xml -->
<sitemapindex>
<sitemap><loc>https://example.com/sitemaps/en.xml</loc></sitemap>
<sitemap><loc>https://example.com/sitemaps/de.xml</loc></sitemap>
<!-- ... 28 آخرون -->
</sitemapindex>
قمنا بإنشاء هذه باستخدام next-sitemap مع إعدادات مخصصة، وتم إعادة إنشاؤها في كل إنشاء.
متى تستخدم هذه المجموعة (ومتى لا)
هذه العمارة -- Next.js + i18n + ISR على Vercel -- قوية، لكنها ليست الخيار الصحيح لكل شيء.
استخدم هذا عندما:
- لديك 10+ لغة مع آلاف الصفحات
- تحديثات المحتوى متكررة لكن ليست في الوقت الفعلي
- تهم الأداء و Core Web Vitals لـ SEO
- الفريق الخاص بك يعرف React/Next.js جيداً
فكر في البدائل عندما:
- لديك أقل من 5 لغات وتحت 1,000 صفحة (قد يكون SSG عادي أبسط)
- المحتوى حقاً في الوقت الفعلي (تداول الأسهم، النتائج الحية) -- استخدم SSR أو جلب جانب العميل
- أنت محدود الميزانية على الاستضافة -- فكر في Astro للمواقع المحلية البحتة بجزء من التكلفة
- الفريق الخاص بك صغير ولا يحتاج إلى تفاعل React -- قد يكون مولد الموقع الثابت مع i18n أقل للاحتفاظ به
للفريق الذي ينظر في مشروع مثل هذا، ساعدنا عدة عملاء مؤسسات في البنية والبناء تطبيقات Next.js واسع النطاق. قرارات العمارة في الأسبوعين الأولين تحدد ما إذا كان المشروع سينجح أو يصبح كابوس الصيانة. إذا كنت تريد التحدث عن الوضع المحدد الخاص بك، تواصل معنا.
الأسئلة الشائعة
كيف يعمل توجيه Next.js i18n مع ISR?
توجيه i18n من Next.js يضيف بادئات منطقة إلى عناوين URL (مثل /fr/products/shoes). عند دمجها مع ISR، يتم تخزين كل مزيج منطقة + صفحة بشكل مستقل في حافة Vercel. إذاً /en/products/shoes و /fr/products/shoes عبارة عن إدخالات ذاكرة تخزين مؤقت منفصلة، لكل منهما مؤقتتها الخاصة بإعادة التحقق. تحصل الدالة getStaticProps على المنطقة في سياقها، وتجلب المحتوى المترجم والمحلي المناسب هناك.
ما هو الحد الأقصى لعدد الصفحات التي يمكن لـ Next.js ISR التعامل معها على Vercel? لا يوجد حد تقني صعب لعدد صفحات ISR التي يمكن لـ Vercel خدمتها. سمعنا عن مشاريع بـ 500+ ألف صفحة. الحدود العملية هي وقت الإنشاء (للصفحات المسبقة)، معدل الإنتاجية الإعادة التحقق، والتكلفة. ذاكرة تخزين مؤقت حافة Vercel مصممة لهذا المقياس -- إنها بشكل أساسي CDN مع إبطال ذكي.
هل يؤثر ISR على SEO للمواقع متعددة اللغات?
لا، صفحات ISR يتم عرضها بالكامل HTML عند خدمتها من ذاكرة التخزين المؤقت، وهو ما يراه محركات البحث الزاحفة. اعتبارات SEO الرئيسية هي علامات hreflang المناسبة، خريطة موقع فهرس منظمة جيداً مع خرائط موقع لكل منطقة، والتأكد من أن إعداد fallback: 'blocking' الخاص بك يمنع الزحافات من رؤية صفحات غير مكتملة. أكد Google أن صفحات ISR/مخزنة مؤقتاً يتم التعامل معها بنفس طريقة HTML الثابت التقليدي.
كيف تتعامل مع تحديثات الترجمة دون إعادة نشر? لمحتوى يدفعه نظام CMS (وصف المنتجات، نسخ التسويق)، الترجمات تتحدث تلقائياً من خلال إعادة تحقق ISR -- إما في المؤقت أو عبر إعادة تحقق عند الطلب webhooks. لترجمات سلسلة UI (تسميات الأزرار، رسائل التحقق من صحة النماذج)، يتم جمعها في وقت الإنشاء، لذلك تتطلب إعادة نشر. نحن نبقي هذه منفصلة بقصد: يجب أن تحديثات المحتوى أبداً لا تتطلب نشر، لكن تغييرات سلسلة UI تمر عبر مراجعة الكود.
ما هو الفرق في التكلفة بين ISR و SSR للمواقع متعددة اللغات على Vercel? SSR ينفذ دالة serverless على كل طلب واحد. في 2M pageviews/month، هذا 2M استدعاء دالة في حوالي 0.40 دولار لكل مليون بعد الطبقة المجانية -- حوالي 800 دولار/شهر في تكاليف الدوال وحدها، بالإضافة إلى النطاق الترددي بشكل كبير لأن هناك ذاكرة تخزين مؤقت أقل. تكلف إعداد ISR الخاص بنا حوالي 715 دولار/شهر إجمالي، بينما SSR المكافئ كان سيكون 2,500-3,500 دولار/شهر.
كيف تتعامل مع تنسيقات التاريخ والأرقام والعملات المختلفة عبر 30 لغة?
نستخدم API Intl المدمج في المتصفح من خلال مرافق تنسيق next-intl. هذا يتعامل مع تنسيق التاريخ (Intl.DateTimeFormat)، تنسيق الأرقام (Intl.NumberFormat)، وعرض العملة بشكل صحيح لكل منطقة. تنسيق رسالة ICU يترك لك تضمين هذه المنسقات مباشرة في سلاسل الترجمة: "price": "From {amount, number, ::currency/EUR}". هذا يعمل من جانب الخادم أثناء إنشاء ISR وجانب العميل للقيم الديناميكية.
هل يجب أن أستخدم App Router أو Pages Router لـ i18n واسع النطاق?
اعتباراً من Next.js 15 (منتصف 2025)، قصة i18n الخاصة بـ App Router نضجت بشكل كبير، و next-intl v4 له دعم App Router ممتاز. للمشاريع الجديدة، أود أن أوصي App Router. يقدم بث أفضل، React Server Components (التي تقلل JavaScript جانب العميل)، والمزيد من عناصر التحكم في الذاكرة المؤقتة الدقيقة. استخدم مشروعنا Pages Router لأنه بدأ في 2024 عندما كان دعم i18n الخاص بـ App Router أقل استقراراً، لكن مشروع greenfield اليوم يجب أن يذهب App Router.
ماذا يحدث إذا فشلت إعادة تحقق ISR? هل يرى المستخدمون صفحة خطأ?
لا، وهذا أحد أفضل ميزات ISR. إذا فشلت إعادة التحقق (ربما يكون API الخاص بـ CMS معطلاً، أو يوجد خطأ الكود في getStaticProps)، يستمر Vercel في خدمة آخر نسخة تم إنشاؤها بنجاح من الصفحة. لا يرى المستخدمون خطأ -- فقط يرون محتوى قديم قليلاً. سجل محاولة إعادة التحقق الفاشلة، والمحاولة التالية لإعادة التحقق ستحاول مرة أخرى. هذا يجعل ISR مرنة بشكل لا يصدق مقارنة بـ SSR، حيث انقطاع API يصبح على الفور انقطاع يواجهه المستخدم.