الهندسة البرمجية
موقع استوديو يُفتح في أقل من ثانية: ميزانية الأداء التي نلتزم بها
كيف نُطلق مواقع استوديو تعرض أول محتوى في أقل من ثانية على هاتف متوسط الفئة عبر شبكة 4G: الميزانية التي نلتزم بها، وما نتخلى عنه، والقرارات المعمارية الصغيرة التي يعتمد عليها أداء Core Web Vitals أكثر من سواها.
في هذا المقال
هناك نوع معيّن من مواقع الاستوديوهات: مع كل تمرير يلمع تأثير منظور متحرك، ويتبدّل الخط، ويبدأ مقطع تلقائي يتوقف للتحميل ثلاث ثوانٍ، وتنبت للمؤشر أنياب حين تضعه فوق العناصر. يُحمَّل هذا الموقع في ثماني ثوانٍ على هاتف متوسط الفئة، ويبدو معطّلًا على كل اتصال ليس من الألياف الضوئية. نحن لا نُطلق مواقع كهذه. الموجز الذي نُلزم به أنفسنا قاسٍ وبسيط: عرض أول محتوى في أقل من ثانية، على هاتف Pixel 6a، عبر اتصال 4G مقيَّد السرعة.
تحقيق هذا الموجز لا يتطلب تحسينات بطولية، بل يعتمد على عدد قليل من الخيارات المعمارية التي يتضاعف أثرها معًا، وعدد أكبر بكثير من الأشياء التي لا نفعلها. يعرض هذا المقال الميزانية التي نعمل وفقها، والقرارات التي تقف وراءها، والقياسات الميدانية التي تثبت أن الميزانية صمدت. إن كنتم تُطلقون موقع استوديو، أو موقع وكالة، أو أي واجهة على شكل معرض أعمال، فهذا هدف واقعي يمكنكم نسخه كما هو تقريبًا.
الميزانية بالأرقام
يستهدف كل موقع استوديو نُطلقه هذه الأرقام على هاتف Pixel 6a مع تقييد السرعة إلى Fast 3G في Chrome DevTools. الواقع على اتصال 4G أسرع؛ ونستخدم Fast 3G حدًّا أدنى لأسوأ الحالات. ونقيس باستخدام Lighthouse، وعبر Vercel Analytics بمقاييس المستخدمين الحقيقيين في بيئة الإنتاج.
- عرض أكبر محتوى (LCP): أقل من 1.2 ثانية في المختبر، وأقل من 1.8 ثانية عند المئين 75 (p75) في الميدان. عتبة «جيد» لدى Google هي 2.5 ثانية.
- عرض أول محتوى (FCP): أقل من 1.0 ثانية في المختبر.
- الإزاحة التراكمية للتخطيط (CLS): أقل من 0.05. عتبة «جيد» لدى Google هي 0.1؛ ونُلزم أنفسنا بنصفها.
- زمن الاستجابة للتفاعل (INP): أقل من 100 ملّي ثانية. عتبة «جيد» لدى Google هي 200 ملّي ثانية.
- إجمالي حجم النقل في التحميل الأول: أقل من 150 كيلوبايت مضغوطة، بما في ذلك HTML وCSS والخطوط وJS والصورة الرئيسية.
- حزمة JavaScript في التحميل الأول: أقل من 80 كيلوبايت مضغوطة.
إن أخفقت صفحة في أي من هذه الأرقام، نعاملها كخلل بأولوية P1. لا كـ«شيء نعود إليه لاحقًا»، بل كخلل P1 نصلحه قبل إطلاق الموقع.
لماذا كل هذه الصرامة؟
لسببين. أولًا، مؤشرات Core Web Vitals عامل ترتيب لدى Google. ليست عاملًا لكسر التعادل، بل عامل ترتيب. المواقع التي تستوفي العتبات تصبح مؤهلة لدفعة تجربة الصفحة؛ والتي لا تستوفيها تُعاقَب على الهامش. ولاستوديو يتنافس على مجموعات كلمات مفتاحية ضيقة، هذا الهامش مهم.
ثانيًا، الأداء المُدرَك إشارة تخص العلامة التجارية. الموقع الذي يُحمَّل فورًا يوحي بالكفاءة. والموقع الذي يتعلّق ريثما يُحلّ بكسل تتبّع لا يوحي بها. ولاستوديو رسالته كلها «نقدّم أعمالًا حديثة ومدروسة»، يجب أن يُثبت الموقع هذه الرسالة من أول تواصل. الموقع هو الموجز نفسه.
القرار 1: عرض كل شيء بشكل ثابت
أكبر قرار منفرد في الأداء هو استراتيجية العرض. نستخدم Next.js مع App Router، وكل صفحة في مواقع الاستوديو التي نبنيها تُعرض بشكل ثابت وقت البناء. لا استعلامات قاعدة بيانات وقت التشغيل. لا عمل على الخادم مع كل طلب سوى دالة الحافة التي تقدّم HTML. الموقع بأكمله ملفات على CDN فحسب.
ما يمنحكم إياه ذلك بشكل ملموس: زمن وصول البايت الأول (TTFB) على الحافة هو ما تقوله شبكة CDN لديكم، عادةً من 30 إلى 80 ملّي ثانية في أي مكان على الكوكب. يصل البايت الأول من HTML قبل أن ينهي المتصفح مصافحة TLS في البديل المعروض على الخادم. وكل ما يلي ذلك، من العرض إلى التفاعلية إلى الصفحة التالية، يبدأ أبكر بالقدر نفسه.
المقابل هو أن تغييرات المحتوى تتطلب نشرًا جديدًا. لموقع استوديو يُحدَّث شهريًا، النشر حدث عابر، إذ يستغرق 90 ثانية على Vercel. أما لمنصة نشر تُحدَّث كل ساعة، فالحسابات مختلفة، والأداة المناسبة هي ISR (Incremental Static Regeneration)، أي التوليد الثابت التدريجي. لكن مواقع الاستوديوهات لا تُحدَّث كل ساعة.
القرار 2: خط ويب واحد بوزنين
الخطوط المخصصة هي أكبر تكلفة يمكن التحكم فيها في العرض الأول. مجموعة فرعية من خط ويب لوزن واحد تبلغ عادةً من 25 إلى 40 كيلوبايت مضغوطة. والموقع الذي يحمّل ثلاثة أوزان من عائلتين يرسل 150 كيلوبايت من الخطوط قبل أن يُعرض أول بايت من المحتوى. نحن نرسل عائلة واحدة بوزنين، تُقدَّم عبر next/font باستضافة ذاتية وأسماء ملفات مُجزَّأة (hashed)، بلا طلب خارجي إلى Google Fonts، وبلا استعلام DNS لشبكة CDN خاصة بالخطوط.
ونتقبّل النتيجة أيضًا: لا خط مائل، ولا رفيع، ولا عريض جدًا. كل تمييز طباعي يجب أن يتحقق بالحجم، أو اللون، أو تباعد الأحرف، أو التباين بين الوزنين المتاحين لدينا. هذا قيد إبداعي يعوّض تكلفته بنفسه. المواقع التي تحتاج إلى ثمانية أوزان تميل إلى إخفاء تسلسل هرمي ضعيف تحت التنوع الطباعي؛ أما المواقع التي تعمل بوزنين فعليها أن تكسب تسلسلها الهرمي بالبنية.
القرار 3: ميزانية الصورة الرئيسية، وكيف تُنفَق
في موقع الاستوديو، يكون LCP دائمًا تقريبًا هو الصورة الرئيسية (hero). لذا فميزانية LCP هي ميزانية الصورة الرئيسية. نضع للصورة الرئيسية سقفًا صارمًا: 80 كيلوبايت مضغوطة عند نقطة توقف سطح المكتب، و40 كيلوبايت على الجوال. وأي حجم أكبر تحدٍّ إبداعي يُحلّ في اختيار الصورة أو معالجتها، لا ذريعة لتجاوز الميزانية.
أربع ممارسات تُبقينا ضمن الميزانية:
- استخدموا AVIF حيثما كان مدعومًا، مع WebP بديلًا احتياطيًا. يكون AVIF عادةً أصغر من WebP بنسبة تتراوح بين 30% و40% بجودة مكافئة.
- قدّموا أحجامًا متجاوبة عبر المكوّن next/image. لا تنزّل الهواتف أبدًا صورة سطح المكتب الرئيسية، ولا تنزّل أجهزة سطح المكتب نسخة retina بدقة 2x إلا إن كانت الشاشة retina فعلًا.
- حدّدوا العرض والارتفاع صراحةً لكل صورة. هذا ما يمنع إزاحة التخطيط، إذ يحجز المتصفح العدد الصحيح من البكسلات قبل وصول الصورة.
- حمّلوا الصورة الرئيسية مسبقًا عبر <link rel="preload" as="image" href="…"/> داخل head. يكلّف ذلك سطرًا واحدًا، ويوفّر من 200 إلى 400 ملّي ثانية في LCP، لأن المتصفح يبدأ طلب الصورة قبل تحليل بقية الصفحة.
القرار 4: JavaScript كملاذ أخير
كل كيلوبايت من JavaScript هو كيلوبايت يجب تنزيله وتحليله وتنفيذه على الخيط الرئيسي قبل أن تصبح الصفحة تفاعلية. نتعامل مع ميزانية JS كمورد شحيح ونلتزم بقاعدتين:
- مكوّنات الخادم افتراضيًا. لا تعلّموا مكوّنًا بـ 'use client' إلا إن كان يحتاج فعلًا إلى التفاعلية. الإعداد الافتراضي في React Server Components هو الإعداد الافتراضي الصحيح للمواقع الغنية بالمحتوى.
- لا سكربتات خارجية في التحميل الأول. لا أدوات تحليلات ترسل 80 كيلوبايت من شيفرة المورّد. لا مديري وسوم. تعمل التحليلات بتحميل مؤجَّل بعد أن تصبح الصفحة تفاعلية؛ ونستخدم Vercel Analytics، الذي لا يتجاوز حجمه نحو 1 كيلوبايت.
الميزانية التي نُلزم بها مواقع الاستوديو لدينا أقل من 80 كيلوبايت من JS المضغوط في التحميل الأول، ومعظمها React نفسه. أما الطبقة التفاعلية (قائمة التنقل، ومبدّل اللغة، ونموذج التواصل) فلا تتجاوز بضعة كيلوبايتات.
القرار 5: بنية CSS لا تُنتج أخطاء إزاحة التخطيط
الإزاحة التراكمية للتخطيط (CLS) هي أسهل مؤشرات Core Web Vitals إخفاقًا عن غير قصد، وأسهلها إصلاحًا عن قصد. العادات الأربع التي تُبقي CLS قريبًا من الصفر:
- احجزوا مساحة لكل ما يصل متأخرًا. الصور، والخطوط، وإطارات iframe، والإعلانات: كلها تحصل على حاوية بنسبة أبعاد وأبعاد صريحة.
- تجنّبوا CSS الذي يعتمد على تحميل الخط. استخدموا خطوط النظام البديلة خلال نافذة التبديل مع font-display: swap. واختاروا خطًا بديلًا تطابق مقاييسه خط الويب حتى يصبح التبديل غير مرئي.
- لا تحقنوا محتوى في الجزء الظاهر أولًا من الصفحة بعد التحميل. اللافتات، وإشعارات ملفات تعريف الارتباط، وأشرطة التنبيهات التي تظهر بعد العرض الأول هي إزاحات تخطيط تنتظر الحدوث. إن احتجتم إليها، فاعرضوها في HTML الأولي وأدخلوها بحركة انتقالية.
- اختبروا على الشبكات البطيئة. مشكلات CLS التي لا تظهر على الألياف الضوئية تظهر على 3G، لأن المورد المتأخر يصل هناك في آخر لحظة.
القرار 6: ميزانية الحركة
ضبط النفس في الحركة هو في حد ذاته تحسين للأداء. كل حركة تعمل على الخيط الرئيسي تُعرّض INP لخطر التراجع؛ فحركة متعثّرة واحدة مدتها 250 ملّي ثانية عند النقر تُخرج الموقع كله من فئة INP «الجيدة». قواعدنا:
- لا تحرّكوا سوى الخاصيتين transform وopacity. كلتاهما تُركَّب على GPU ولا تُطلق إعادة حساب التخطيط.
- حركات الظهور مكتوبة بلغة CSS، لا بلغة JavaScript. نستخدم IntersectionObserver لإضافة className؛ أما الحركة نفسها فتنفّذها خاصية transition في ورقة الأنماط.
- لا حركات مرتبطة بالتمرير. تبدو أنيقة لكنها تجلب أداء تمرير سيئًا على الأجهزة متوسطة الفئة.
- لا فيديو يعمل تلقائيًا في القسم الرئيسي. إن احتجتم إلى فيديو، فحمّلوه تحميلًا مؤجَّلًا أسفل الجزء الظاهر من الصفحة أو خلف تفاعل.
القرار 7: تقاطع إمكانية الوصول والأداء
معظم مكاسب إمكانية الوصول هي مكاسب في الأداء أيضًا. لغة HTML الدلالية أصغر من فوضى عناصر div. وحلقات التركيز الأصلية في المتصفح تُعرض أسرع من إدارة التركيز المخصصة بـ JS. وروابط التخطي في رأس الصفحة تُغني عن إصلاحات مصائد لوحة المفاتيح التي تتطلب JavaScript. نصمّم ونبني لإمكانية الوصول أولًا؛ ويأتي الأداء مجانًا.
المكان الوحيد الذي يتوتر فيه الأداء وإمكانية الوصول هو الحركة. بعض المستخدمين يحتاجون إلى الحركة؛ وآخرون يشعرون بالغثيان منها. نحترم إعداد prefers-reduced-motion، ونُطلق كل حركة ظهور مع بديل يعتمد على التلاشي، لا على الإزاحة.
القياس في بيئة الإنتاج
مقاييس المختبر من Lighthouse ضرورية لكنها غير كافية. المعيار الذهبي هو مراقبة المستخدمين الحقيقيين (RUM)، أي ما يختبره الزوار الفعليون على أجهزتهم الفعلية. نستخدم Vercel Analytics لمؤشرات Web Vitals لأنه يأتي مجانًا مع المنصة، ويقدّم تقارير بالمقاييس الميدانية مقسّمة حسب المسار والجهاز والموقع الجغرافي.
ما نراجعه أسبوعيًا:
- قيمة LCP عند المئين 75 لكل مسار. إن تجاوزت قيمة p75 لأي مسار 1.8 ثانية، نتحقق من السبب.
- تراجعات INP. يُعدّ INP أخبث المقاييس، إذ قد يتدهور بصمت كلما ازداد حجم JavaScript في الموقع.
- مؤشر CLS لكل مسار. تعود تراجعات CLS عادةً إلى عنصر واحد يُحمَّل متأخرًا.
- حجم JS في التحميل الأول لكل مسار، نتتبعه عبر محلل الحزم في CI. ونُفشل أي بناء يتجاوز الميزانية.
ما لا نفعله
ولا يقل أهمية: الأشياء التي نتركها عمدًا، رغم أنها معيار شائع في القطاع:
- لا توجيه من جهة العميل على طريقة تطبيقات الصفحة الواحدة (SPA) في موقع صغير. جلب المستند الكامل أسرع من تنزيل فرق الحزمة على اتصال 4G. ونترك التنقل للمتصفح.
- لا عامل خدمة (service worker). التعقيد لا يستحق العناء في موقع لا تبلغ صفحاته العشر.
- لا خدمة استضافة صور تحقن حزمة SDK بحجم 30 كيلوبايت. نقدّم الصور من نطاق الموقع نفسه، محسّنة وقت البناء.
- لا مكتبة «ذكية» للتحميل المؤجَّل. الخاصية الأصلية loading="lazy" تؤدي المهمة بصفر كيلوبايت.
- لا جلب مسبق لكل رابط عند تمرير المؤشر فوقه. يجلب Next.js الروابط مسبقًا افتراضيًا في بيئة الإنتاج؛ ونترك ذلك مفعّلًا ونترك القرار لإطار العمل.
راسلونا على hello@neptay.com إن كنتم تخططون لموقع وتريدون تدقيقًا لأداء ما لديكم، أو تريدون موقعًا يُبنى وفق هذه الميزانية من البداية.