قواعد التخمين المسبق (Speculation Rules) في Next.js: كيف نعرض التنقلات الكاملة مسبقًا دون كسر التحليلات أو سياسة CSP
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗العنوان الرئيسي: تتيح واجهة Speculation Rules API للمتصفحات المبنية على Chromium جلب الصفحة التالية المتوقعة مسبقًا (prefetch) أو عرضها بالكامل في الخلفية (prerender) قبل أن ينقر المستخدم. في تطبيقات Next.js App Router لا تُسرّع هذه الواجهة إلا التنقلات الكاملة للمستند، لذلك نحصر القواعد في تلك الروابط، ونستخدم درجة الاستعجال moderate، ونستبعد كل رابط له آثار جانبية، ونؤجل التحليلات حتى تُفعَّل الصفحة المعروضة مسبقًا.
أهم النقاط
• واجهة Speculation Rules API ميزة في متصفحَي Chrome وEdge تجلب المستند التالي مسبقًا أو تعرضه مسبقًا في تبويب مخفي، ويتم ضبطها عبر عنصر script من نوع speculationrules أو عبر ترويسة Speculation-Rules.
• الجلب المسبق في مكوّن Link في Next.js وقواعد التخمين المسبق يحلّان مشكلتين مختلفتين: مكوّن Link يُسرّع التنقلات من جهة العميل، بينما تُسرّع قواعد التخمين التنقلات الكاملة للمستند فقط.
• في Devya نحصر قواعد المستند في روابط التنقل الكامل، لأن قاعدة شاملة مثل /* تطابق أيضًا كل رابط ينتجه مكوّن Link وتتسبب في عمليات عرض على الخادم يتجاهلها موجّه العميل.
• درجة الاستعجال moderate هي إعدادنا الافتراضي: يبدأ Chrome التخمين بعد تمرير المؤشر 200 ميلي ثانية أو عند حدث pointerdown، ولا يحتفظ إلا بتخمينين من هذا النوع في الوقت نفسه.
• الصفحات المعروضة مسبقًا تشغّل JavaScript قبل أن يراها المستخدم، لذا يجب أن تنتظر أحداث مشاهدة الصفحة حتى تصبح قيمة document.prerendering مساوية لـ false، ويجب استبعاد الروابط ذات الآثار الجانبية.
ما هي واجهة Speculation Rules API؟
واجهة Speculation Rules API هي واجهة من واجهات منصة الويب تسمح للصفحة بأن تعلن عن الروابط التي يمكن للمتصفح تحميلها قبل حدوث التنقل. وهي تقدم إجراءين: الجلب المسبق (prefetch) الذي ينزّل HTML المستند التالي فقط دون موارده الفرعية ودون تنفيذ JavaScript، والعرض المسبق (prerender) الذي يحمّل الصفحة كاملة بمواردها وسكربتاتها ويعرضها في تبويب خلفي غير مرئي، ثم يضعها مكان الصفحة الحالية عند النقر.
تأتي القواعد بشكلين. قواعد القائمة (list rules) تحدد روابط بعينها. أما قواعد المستند (document rules) فتطابق الروابط الموجودة في الصفحة الحالية عبر شرط where: الشرط href_matches لأنماط الروابط، والشرط selector_matches لمحددات CSS، مع إمكانية دمجها عبر and وor وnot. ويحدد الحقل eagerness متى يبدأ Chrome العمل.
• immediate: يبدأ التخمين فور قراءة القواعد، ويسمح Chrome بما يصل إلى 50 عملية جلب مسبق و10 عمليات عرض مسبق.
• eager: على سطح المكتب، ومنذ الإصدار Chrome 143، يكفي بقاء المؤشر على الرابط نحو 10 ميلي ثانية؛ وعلى الجوال يبدأ بعد وقت قصير من ظهور الرابط في الشاشة؛ مع الحدود نفسها 50 و10.
• moderate: على سطح المكتب، تمرير المؤشر 200 ميلي ثانية أو حدث pointerdown؛ وعلى الجوال، استدلالات مبنية على منطقة العرض؛ وبحد أقصى تخمينان في الوقت نفسه، ويُستبعد الأقدم أولًا.
• conservative: عند حدث pointerdown أو touchstart؛ وبحد أقصى تخمينان في الوقت نفسه، ويُستبعد الأقدم أولًا.
القيمة الافتراضية لقواعد القائمة هي immediate، ولقواعد المستند هي conservative. يدعم Chrome وEdge هذه الواجهة، بينما يتيحها Safari خلف خيار تجريبي، ولا يدعمها Firefox. المتصفحات غير الداعمة تتجاهل القواعد تمامًا، ما يجعل قواعد التخمين تحسينًا تدريجيًا لا يحتاج إلى أي شيفرة بديلة.
هل يجعل الجلب المسبق في مكوّن Link في Next.js قواعدَ التخمين غير ضرورية؟
لا. مكوّن Link في Next.js وواجهة Speculation Rules API يُسرّعان نوعين مختلفين من التنقل. النقر على Link داخل التخطيط الجذري نفسه هو تنقل خفيف (soft navigation): يجلب App Router حمولة React Server Component (RSC) ويحدّث الصفحة عبر history.pushState، فلا يطلب المتصفح مستندًا جديدًا أبدًا. لذلك لا يفيد المستند المعروض مسبقًا في هذه النقرة، ولا تساعد قواعد التخمين إلا عندما يجري المتصفح تنقلًا كاملًا للمستند.
• الجلب المسبق في مكوّن Link: يجلب حمولة RSC، ويُسرّع التنقلات الخفيفة، ويعمل في جميع المتصفحات.
• الجلب المسبق عبر قواعد التخمين: يجلب مستند HTML فقط، ويُسرّع التنقلات الكاملة في متصفحات Chromium.
• العرض المسبق عبر قواعد التخمين: يحمّل الصفحة كاملة، ويشغّل JavaScript الخاص بها قبل التفعيل، ويُسرّع التنقلات الكاملة في متصفحات Chromium.
في مشاريع Next.js التي يبنيها فريقنا، تأتي التنقلات الكاملة من أربعة مصادر: الروابط العادية داخل HTML القادم من نظام إدارة المحتوى أو من Markdown والتي لا يعترضها App Router؛ والروابط إلى تطبيق Next.js منفصل على النطاق نفسه عبر multi-zones، مثل تطبيق توثيق تحت /docs؛ والروابط بين تخطيطين جذريين مختلفين، والتي يحوّلها App Router إلى تحميل كامل للصفحة؛ والروابط من صفحة هبوط ثابتة إلى تطبيق Next.js.
الفخ الشائع هو القاعدة الشاملة. قاعدة المستند التي تستخدم href_matches بالقيمة /* تطابق كل رابط في الصفحة، ومكوّن Link في Next.js ينتج عنصر رابط حقيقيًا. ومع درجة moderate يؤدي كل تمرير للمؤشر إلى عرض مستند كامل على الخادم، ثم يجري موجّه العميل تنقلًا خفيفًا يتجاهل ذلك العرض المسبق. والنتيجة عملية عرض على الخادم مع كل تمرير دون أي فائدة للمستخدم.
كيف نضيف قواعد التخمين إلى تطبيق Next.js App Router؟
أبسط طريقة هي عنصر script مضمّن من نوع speculationrules يُعرض من التخطيط الجذري. نحتفظ بالقواعد في وحدة واحدة ونحصرها في الروابط التي تسبب تنقلات كاملة فعلًا: قاعدة عرض مسبق بدرجة moderate للمسارات التابعة لمنطقة منفصلة مثل /docs/*، مع استبعاد أي رابط يحمل السمة data-no-prerender، وقاعدة جلب مسبق بدرجة conservative للروابط التي تختار ذلك صراحةً عبر السمة data-hard-nav.
تحظر سياسة أمان المحتوى (CSP) قواعد التخمين المضمّنة ما لم يسمح بها التوجيه script-src. وهناك ثلاثة خيارات: قيمة nonce، أو بصمة hash لنص JSON، أو الكلمة المفتاحية 'inline-speculation-rules' التي تسمح بـ JSON قواعد التخمين دون السماح بأي JavaScript مضمّن آخر. في صفحاتنا المعتمدة على nonce، يقرأ التخطيط قيمة nonce من ترويسات الطلب التي يضبطها الـ middleware نفسه الذي يكتب ترويسة CSP، فتبقى السياسة صارمة.
للصفحات المعروضة بشكل ثابت نفضّل ترويسة الاستجابة Speculation-Rules. تشير هذه الترويسة إلى ملف JSON يجلبه المتصفح بشكل منفصل، فلا يحتاج التخطيط إلى nonce لكل طلب. نقدّم ملف JSON هذا من route handler ثابت في Next.js بنوع المحتوى application/speculationrules+json، لأن المتصفح يتجاهل ملفات القواعد المقدّمة بأي نوع آخر، ونضيف الترويسة إلى كل المسارات عبر الدالة headers() في الملف next.config.ts. ويمكن الجمع بين القواعد المضمّنة وقواعد الترويسة، إذ يدمج المتصفح كل مجموعات القواعد التي يجدها.
ما الذي يتعطل عندما يعرض المتصفح صفحة مسبقًا؟
الصفحة المعروضة مسبقًا تنفّذ JavaScript قبل أن يقرر المستخدم زيارتها، لذلك تصبح خاطئة كل شيفرة تفترض أن التحميل يعني المشاهدة. نراجع أربعة جوانب قبل تفعيل العرض المسبق في أي مشروع لعملائنا.
روابط GET ذات الآثار الجانبية: روابط تسجيل الخروج، وإلغاء الاشتراك بنقرة واحدة، وتسجيل الدخول السحرية، ووضع علامة «مقروء» يجب ألا تخضع للتخمين أبدًا، لأن الجلب المسبق والعرض المسبق طلبات GET حقيقية. نستبعدها بشروط not أو بالسمة data-no-prerender.
التحليلات: حدث مشاهدة الصفحة الذي يُرسل عند التحميل يحسب عمليات عرض مسبق لم يرها المستخدم قط. والحل هو انتظار التفعيل: تكون قيمة document.prerendering مساوية لـ true أثناء العرض المسبق، ويُطلق الحدث prerenderingchange عندما ينتقل المستخدم إلى الصفحة فعلًا. تتحقق دالة تتبّع المشاهدات لدينا من document.prerendering، وإذا كانت true تسجّل مستمعًا لمرة واحدة للحدث prerenderingchange قبل إرسال الحدث. بعض مكتبات التحليلات تعالج ذلك مسبقًا، لذا نتحقق من سلوك المزوّد قبل إضافة حماية خاصة بنا.
العمل على الخادم: تحمل الطلبات التخمينية ترويسة الطلب Sec-Purpose بالقيمة prefetch للجلب المسبق، وprefetch;prerender للعرض المسبق. نقرأها في الـ middleware لوسم الطلبات التخمينية في السجلات والمقاييس. وبما أن العميل هو من يرسل هذه الترويسة، فإننا لا نستخدمها أبدًا لتجاوز حدود معدل الطلبات أو التحقق من الصلاحيات.
الروابط عبر الأصول المختلفة: لا يعرض Chrome مسبقًا افتراضيًا إلا الروابط من الأصل نفسه (same-origin)، ويجب على النطاق الفرعي من الموقع نفسه، مثل app.example.com، أن يوافق صراحةً عبر ترويسة الاستجابة Supports-Loading-Mode: credentialed-prerender. وتتجنب multi-zones في Next.js على نطاق واحد هذا القيد لأن كل المناطق تتشارك الأصل نفسه.
كيف نتحقق من أن قواعد التخمين تعمل؟
تعرض أدوات مطوري Chrome كل عملية تخمين في لوحة Application، ثم Background services، ثم Speculative loads: مجموعات القواعد التي تمت قراءتها، والروابط التي جُلبت أو عُرضت مسبقًا، وسبب الفشل لكل تخمين أُلغي. نراجع هذه اللوحة بعد كل تعديل على القواعد، لأن مجموعة القواعد غير الصالحة تفشل بصمت.
في مراقبة المستخدمين الحقيقيين (RUM)، تكون قيمة activationStart في سجل Navigation Timing أكبر من صفر عندما تُعرض الصفحة مسبقًا ثم تُفعَّل. وتُبلغ مكتبة web-vitals عن نوع التنقل prerender لهذه التنقلات، وتقيس Largest Contentful Paint (LCP) بدءًا من لحظة التفعيل. نقسّم قيم LCP حسب نوع التنقل حتى لا يذوب التحسن في المتوسط مع المتصفحات التي تتجاهل القواعد.
التكلفة لا تقل أهمية عن السرعة. نقارن عدد الطلبات التي تحمل الترويسة Sec-Purpose: prefetch;prerender بعدد مرات التفعيل؛ فالفجوة الكبيرة تعني أن القواعد مستعجلة أو واسعة أكثر من اللازم، وعندها ننتقل من moderate إلى conservative أو نضيّق أنماط href_matches قبل تغيير أي شيء آخر.
الأسئلة الشائعة
س: هل تعمل قواعد التخمين في Safari وFirefox؟
ج: ليس افتراضيًا. يدعم Chrome وEdge واجهة Speculation Rules API، ويتيحها Safari خلف خيار تجريبي، ولا يدعمها Firefox. المتصفحات غير الداعمة تتجاهل عنصر speculationrules، لذا لا تتسبب إضافته في أي عطل لديها.
س: أيهما الخيار الافتراضي الأكثر أمانًا: العرض المسبق أم الجلب المسبق؟
ج: الجلب المسبق أكثر أمانًا لأنه ينزّل مستند HTML فقط ولا يشغّل أي JavaScript. العرض المسبق يجعل التفعيل أسرع لكنه ينفّذ الصفحة مبكرًا، لذا يجب إصلاح التحليلات ومعالجة الآثار الجانبية أولًا.
س: هل يمكن أن تضر قواعد التخمين بالأداء؟
ج: نعم، عندما تكون واسعة أكثر من اللازم. القواعد المستعجلة على صفحات مليئة بالروابط تهدر عرض النطاق وسعة الخادم وذاكرة الجهاز، ولهذا يضع Chrome حدًا لعدد التخمينات المتزامنة، ولهذا نبدأ بدرجة moderate على مجموعة ضيقة من الروابط.
قراءات إضافية
هندسة الواجهات
الوضع الداكن دون وميض في Next.js 16: كيف نستخدم سكربت سمة حاجبًا وcolor-scheme وزر تبديل آمنًا مع الـ Hydration
وميض الوضع الداكن عند إعادة تحميل الصفحة مشكلة توقيت: الخادم لا يستطيع قراءة localStorage. نشرح سكربت السمة الحاجب الذي نستخدمه في Next.js 16، ولماذا لا نضع السمة في cookie حفاظًا على الرسم الثابت، وكيف نبني زر تبديل يمر بالـ hydration دون أي عدم تطابق.
هندسة الواجهات
صور Open Graph الديناميكية في Next.js 16: كيف نبني opengraph-image.tsx ونتحقق من إصدار Satori الذي تضمّنه next/og ونحافظ على صحة معاينات الروابط
واجهت بطاقات المشاركة المولّدة لدينا ثلاث مفاجآت: خط WOFF2 لم يستطع المحرّك قراءته، وملف README لـ Satori يصف محرّكًا أحدث من المضمّن في Next.js، وصفحة ظلّت تعرض صورة الـ layout. هذه ملاحظاتنا الميدانية حول opengraph-image.tsx في Next.js 16 وأولوية البيانات الوصفية وفحص og:image قبل مشاركة أي رابط.