إذا لاحظت أن زوار موقعك يغادرون بسرعة قبل تحميل الصفحة، فأنت لست وحدك. بطء تحميل الموقع مشكلة تؤثر على تجربة المستخدم وترتيب نتائج البحث وحتى المبيعات. الكثير من الحلول المنتشرة تركز على أدوات مدفوعة أو تعديلات معقدة، لكن السبب الأساسي في أغلب الحالات بسيط ويمكن إصلاحه في دقائق. هذا المقال يوضح لك أين تبدأ، وما الذي تتجاهله معظم الشروحات.
ربما لاحظتَ أن بعض صفحات الويب التي تملكها تستغرق أكثر من 10 ثوانٍ للتحميل – لستَ وحدك. صفحات الويب البطيئة تُسبب معدلات ارتداد أعلى، وهذا أمرٌ سيئ. الأدوات الحديثة والتقنيات التقليدية هي السبب. لحسن الحظ، مع القليل من المعرفة والاستراتيجية الذكية، يمكنك بسهولة التغلب على هذه المشكلة الشائعة.
يجب أن يكون هدفك العام هو الحفاظ على صفحات الويب خفيفة، وتجنب كثرة طلبات الشبكة أثناء تحميل الصفحة الأولى، والتركيز على عرض محتوى الصفحة بأسرع ما يمكن. جهّز نفسك بالتقنيات التالية، وتجنب الأساليب التقليدية مثل أطر العمل والمكتبات التي تُضخّم حجم صفحتك. أسلوبك المميز سيُميّزك عن منافسيك.
تجنب الأطر المُضخّمة
في منشور سابق قدّم نصائح لمطوّري الويب المبتدئين، اقترحتُ عليهم تجنّب أطر العمل لصعوبة تعلّمها، ولكون تشغيلها مُرهِقًا للموارد. تُعيد مُعظم أطر العمل ابتكارَ الحلول بتفويض الكثير من العمل إلى جافا سكريبت، وهو أمرٌ غير مثالي.
يجب أن تكون استراتيجيتك صفحة ويب سريعة وفعّالة، لذا قد يكون إطار العمل الخيار الخاطئ. تتمثل المزايا الأساسية لإطار العمل في تطبيقات الصفحة الواحدة، وتنظيم أفضل للكود، وتحديثات DOM أكثر كفاءة – أول اثنين منها مُثيران للجدل، والثالث صحيحٌ فقط في بعض الحالات.
أستخدم شخصيًا مكونات الويب للمشاريع الصغيرة والأكثر تركيزًا. فهي ليست أقل حجمًا فحسب، بل ستستمر مكونات الويب في العمل حتى بعد انتهاء React وAngular وVue بوقت طويل لأنها جزء من معيار W3. إطار عمل Lit (من جوجل) هو غلاف خفيف لمكونات الويب يبدو واعدًا جدًا؛ فهو يُعالج بعض الشيفرات النمطية (الكود المُمل والمُكرر).
لذا، إن أمكن، تجنّب الأطر (الثقيلة)؛ فالكثير من النصائح التالية تفترض ذلك. مع ذلك، إذا استخدمت إطار عمل (مثل React)، فقد لا تنطبق بعض الأمور، لكنها أساسية مع ذلك.
استخدم تبعيات أقل وأخف
عند تحميل الصفحة في البداية، تُعدّ طلبات الشبكة لسحب التبعيات مُساهمًا رئيسيًا في ضعف الأداء. أبذل قصارى جهدي لتجنب التبعيات بأي ثمن، وخاصةً المكتبات المساعدة مثل Lodash أو Ramda. للأسف، هذا يعني استخدام حلول مُخصصة لبعض الأشياء.
على سبيل المثال، في إحدى المرات، احتجتُ إلى ناقل أحداث بسيط، وهو فئة تُمثّل في الأساس قناة اتصال مُشتركة عبر الكود الخاص بي. أحد الحلول الشائعة هو مكتبة تُسمى RxJS، ولكن بدلًا من تضمينها كتبعية، والتي يبلغ حجمها 17.7 كيلوبايت (مضغوطة)، كتبتُ فئة خاصة بي بحجم 200 بايت. هذا يُوفّر 100 مللي ثانية فقط من وقت التحميل الأولي، لكن جميعها تُضاف.
الطريقة التقليدية لكتابة الكود للمتصفح تتضمن تجميعه. تتطلب هذه العملية كتابة الكود بلغة Node.js واستخدام أداة، مثل webpack، لتحويله إلى صيغة يمكن للمتصفح استخدامها. والنتيجة هي كتلة واحدة كبيرة من الكود تتضمن كل شيء، وغالبًا ما يزيد حجمها عن 100 كيلوبايت. تطورت تقنيات لمعالجة هذه المشكلة، مثل تقسيم الكود، الذي يقسم الكود إلى أجزاء؛ ثم يقوم إطار العمل أو المتصفح بتنزيلها فقط عند الحاجة إليها. وهناك تقنية أخرى وهي Tree-shaking، التي تحلل الكود لاستخراج ما هو مطلوب فقط؛ وتعمل ESM بطريقة مشابهة إلى حد ما.
ESM هو نظام وحدات JavaScript الحديث، وهو الطريقة القياسية الجديدة لاستيراد المكتبات. إليك مثال على ESM:
<script type="module">
const {foo} from "https://example.com/all-the-things.js"
</script>
سيقوم المثال السابق بتنزيل الوحدة المطلوبة وأي وحدات أخرى تعتمد عليها. هذه هي آلية عمل ESM، وهناك بعض أوجه التشابه الغامضة مع اهتزاز الشجرة.
ESM ليس حلاً سحريًا، وله بالتأكيد حدوده. مع أنني أنصحك باستخدامه، إلا أنني أتمسك بنصيحتي السابقة بكتابة حلولك البسيطة بنفسك (حيثما أمكن) – فهذا يعني تجنب شجرة تبعيات متضخمة لمكتبات الجهات الخارجية. إذا استغرقت الكتابة أقل من 30 دقيقة، فلماذا لا؟
أحد حدود ESM هو أنه إذا كانت الوحدة تعتمد على العديد من الوحدات الأخرى، فيجب على المتصفح تنزيلها جميعًا – تُسمى هذه المجموعة شجرة تبعيات. على سبيل المثال، يتطلب استيراد دالة capitalize من Lodash تحميل العديد من الملفات الإضافية:
<script type="module">
import capitalize from 'https://cdn.jsdelivr.net/npm/[email protected]/capitalize.js';
alert(capitalize("hello world!"))
</script>
يمكنك أن ترى من الصور أن استخدام Lodash لكتابة الحرف الأول من الكلمة بأحرف كبيرة سيُجري 23 طلبًا ويستغرق 4.4 ثانية على اتصال GPRS (20 كيلوبايت/ثانية)؛ بينما على اتصال 100 ميجابايت، فإنه لا يزال يستغرق 2.2 ثانية. إذا كانت سرعة اتصال أعلى بـ 5000 مرة تؤدي فقط إلى تنزيل أسرع بمرتين، فهذا يشير إلى أن النطاق الترددي ليس هو المشكلة.
في الصورة الثالثة، يُظهر القسم المسمى 1 أن المتصفح يُرسل الطلبات بشكل متتالي، مُنزِّلًا فقط الكود الذي يحتاجه (يشبه إلى حد ما عملية اهتزاز الشجرة). يُنزِّل المتصفح كل ملف، ويتحقق من وارداته (تبعياته)، ثم يُنزِّلها. يستخدم الاتصال HTTP/2، الذي يعمل أسرع بكثير من HTTP/1 في بعض السيناريوهات. وذلك لأن HTTP/2 يُحافظ على اتصال واحد دائم، بينما في بعض الحالات، يعتمد HTTP/1 على اتصالات متعددة منفصلة، كل منها ينطوي على تكلفة تفاوض إضافية. تخيل الآن أن متصفحًا قديمًا يُحمّل صفحة الويب هذه، ويستخدم HTTP/1 لتحميل كل وحدة.
لكتابة الحرف الأول من الجملة بحرف كبير، لا يتطلب الأمر سوى القليل من التعليمات البرمجية:
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
يُفضّل تطبيق حل مُخصّص إذا كان سهلاً؛ فهذا يُبقي الكود خفيفًا ومُركّزًا على أهدافك. بالإضافة إلى ذلك، تذكّر أن عمليات الاستيراد مُكلفة عند استخدام ESM، وعليك التفكير مليًا في كيفية تقسيم الكود؛ حاول تقليل التبعيات بين الوحدات. كما أن التجميع لا يزال طريقة فعّالة جدًا لتقليل عدد الطلبات التي يُجريها المُتصفّح. هناك الكثير مما يجب مراعاته، لذا خصّص بعض الوقت لهذا.
الخلاصة هنا هي استخدام التبعيات باعتدال. تُشكّل أطر العمل تبعيات كبيرة، والمكتبات مسؤولية. الهدف هو العمل بسرعة وكفاءة، وزمن وصول الشبكة سيؤثر سلبًا على أدائك.
استخدم defer أو async على وسوم النصوص البرمجية
يُعدّ كلٌّ من defer وasync سمتين يُمكنك استخدامهما على وسوم النصوص البرمجية لتغيير سلوكها. كلاهما يؤثر على الأداء، لكنهما يعملان بشكل مختلف قليلاً عن بعضهما البعض، وهناك حالات استخدام واضحة لكل منهما.
باستثناء defer وasync وESM حاليًا، يُرجى ملاحظة أن جميع وسوم النصوص البرمجية تُنزَّل بالتوازي مع بعضها البعض، ولكنها تُنفَّذ قبل مُحلِّل HTML. هذا هو سلوك وسوم النصوص البرمجية عادةً.
باستخدام خادم ويب مخصص، تعمدتُ ضبط تأخيرات التنزيل للنصين 1 و2. في الشرح التوضيحي 5، لاحظ أن كلا النصين بدأا التنزيل في نفس الوقت تقريبًا، وبدأ النص الذي استغرق تأخيره 2000 مللي ثانية أولاً. لو لم تكن عمليات التنزيل تتم بالتوازي، لكان النص الأطول (2000 مللي ثانية) قد منع تنزيل النص الآخر حتى انتهاءه.
بالنظر إلى الشرح التوضيحي 4، يمكنك أيضًا ملاحظة أن HTML (الشرح التوضيحي 3) لم يُحلَّل إلا بعد انتهاء جميع النصوص. هذا يُظهر أن النصوص تحظر مُحلِّل HTML أثناء تنزيلها وتنفيذها.
تأجيل
تُمكِّن سمة التأجيل في وسوم النصوص المتصفح من تأخير تنفيذ النص حتى بعد عرض HTML. هذا مهم، لأننا لا نريد حظر محتوى الصفحة الأولي أثناء انتظار تنزيل وتنفيذ شيفرة JavaScript كثيفة. لاحظ أيضًا أن النصوص المؤجلة تُنفَّذ بالترتيب الذي تظهر به في HTML.
<html>
<head>
<script src="/js/foo.js" defer></script>
</head>
<p>I render before the script.</p>
</html>
نصيحة
ESM مؤجل أيضًا افتراضيًا.
يُشغّل البرنامج النصي المؤجل بعد اكتمال تحميل HTML، مما يُتيح لك الوصول إلى DOM بأمان. إذا كنت بحاجة إلى تشغيل الكود بعد انتهاء جميع البرامج النصية المؤجلة، فاستخدم الحدث DOMContentLoaded، حيث يُشغّل بمجرد تحليل HTML وتشغيل جميع البرامج النصية المؤجلة.
// /js/foo.js
document.addEventListener("DOMContentLoaded", () => {
console.log("The DOM is fully loaded!")
})
مع ذلك، لا تنتظر دالة DOMContentLoaded الصور أو النصوص البرمجية غير المتزامنة أو الإطارات المضمنة.
نصيحة
تنتظر النصوص البرمجية المؤجلة حتى يُنزّل المتصفح جميع أوراق الأنماط ويُحللها، وهو ما يختلف عن سلوك وسم النصوص البرمجية الاعتيادي.
هذا كثيرٌ مما يجب تذكره، ولكن ببساطة: تنتظر دالة DOMContentLoaded اكتمال مُقدّم HTML وأوراق الأنماط أولًا. للتأكد تمامًا من جاهزية الصفحة، استمع إلى دالة DOMContentLoaded.
async
تُخبر سمة async المتصفح بتنزيل النص البرمجي أثناء تحليل HTML. يُمكن تشغيل النص البرمجي في أي وقت. لا ينتظر النص البرمجي ومحلل HTML بعضهما البعض، ولكن قد يُقاطع النص البرمجي محلل HTML إذا لزم الأمر. إنه بمثابة تقاطع بين النص البرمجي العادي والمؤجل.
<head>
<script src="/js/foo.js" async></script>
</head>
ملخص
- يتم تنزيل البرامج النصية المؤجلة (وESM) فورًا وتُنفَّذ بعد مُحلِّل HTML وأوراق الأنماط.
- يتم تنزيل البرامج النصية غير المتزامنة فورًا وتُنفَّذ في أي وقت، مما قد يُقاطع مُحلِّل HTML.
- يتم تنزيل البرامج النصية العادية فورًا وتُنفَّذ قبل مُحلِّل HTML.
توضح الصورة التالية الجداول الزمنية لكل نوع من أنواع البرامج النصية، مُشيرةً إلى وقت تنزيلها وتنفيذها بالنسبة لمُحلِّل HTML:
استخدم نصوصًا غير متزامنة لنصوص خارجية معزولة، مثل الإعلانات – وهي نصوص لا تتفاعل مع بقية صفحتك. استخدم النصوص المؤجلة قدر الإمكان. سيكون استخدام النصوص المؤجلة بالغ الأهمية في القسم التالي، الذي يناقش CSS الهام.
CSS الهام المضمن
عند عرض صفحة ويب لأول مرة، نقول إن الأنماط المرئية على الشاشة تكون أعلى الطية، والأنماط الموجودة أسفل الشاشة تكون أسفلها. يُعنى CSS الهام بعرض أنماط أعلى الطية بأسرع ما يمكن لإعطاء المستخدم انطباعًا بسرعة تحميل الصفحة وتجنب اختبار صبره. هذا مهم لأنه كلما طال انتظار المستخدمين لتحميل الصفحة، زاد احتمال مغادرتهم.
لعرض المحتوى أعلى الطية بأسرع ما يمكن، نحتاج إلى تقسيم أنماطنا إلى حزمتين، واحدة للمحتوى أعلى الطية والأخرى للمحتوى أسفلها. لتحقيق ذلك، يكفي تحليل الأنماط يدويًا لكل قسم – لأجهزة الكمبيوتر والهواتف المحمولة.
لتقسيم أنماطك إلى حزمتين، يبدو المثال الأساسي على النحو التالي:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="below-the-fold.js" defer></script>
<style>
/* Critical CSS. */
</style>
</head>
<body>
<aside>Above the fold.</aside>
<main>
<p>Below the fold.</p>
</main>
<link rel="stylesheet" type="text/css" href="below-the-fold.css">
</body>
</html>
ولجعله أكثر قابلية للقراءة، فإن المثال السابق غير مكتمل؛ ويبدو المثال الواقعي على هذا النحو:
يحتوي وسم النمط في القسم 1 على شيفرة CSS المهمة، والتي يجب أن تتضمن أنماط أعلى الطية. في مثالنا، يحتوي وسم “side” (القسم 6) على جميع المحتوى الموجود أعلى الطية. إذا كنت ترغب في تنسيقه، فعدّل القسم 1.
عند عرض الصفحة لأول مرة، لا يحتوي المحتوى الموجود أسفل الطية على أي أنماط، لذا سيظهر وميضًا قصيرًا، مُظهرًا محتوى غير منسق – يُسمى هذا وميضًا للمحتوى غير المنسق (FOUC). لمعالجة هذه المشكلة، أخفيتُ المحتوى بدرجة تعتيم 0 (القسم 1). عند تشغيل جافا سكريبت (القسم 4)، سيُغيّر هذه التعتيم إلى 1. يوجد تأثير انتقال سلس، لذا سيتلاشى بشكل رائع.
نظرًا لأن أوراق الأنماط في عنصر الرأس تحظر مُقدّم HTML، فإننا نُحمّل بدلاً من ذلك شيفرة CSS غير المهمة قرب نهاية النص (القسم 3) بعد عرض جميع شيفرة HTML الأخرى. باختصار، يُقدّم المتصفح HTML أولًا ثم يُنزّل ورقة الأنماط في نهاية النص، قبل أن يُنفّذ البرنامج النصي المؤجل.
ملاحظة
نستخدم سكربتًا مؤجلًا لأنه يُنفَّذ بعد انتهاء عرض HTML بالكامل، وبعد تنزيل جميع أوراق الأنماط وتطبيقها.
تبدو العملية كالتالي:
- يطلب المتصفح HTML.
- يبدأ المتصفح بتحليل المستند.
- يُنزِّل المتصفح جافا سكريبت في الخلفية، ثم ينتقل مباشرةً (القسم 5).
- يُحلِّل المتصفح الأنماط المضمنة (القسم 1).
- يُعرِّض المتصفح وسم “side”، الموجود أعلى الطية (القسم 6).
- يُعرِّض المتصفح المحتوى الرئيسي (القسم 2)، الموجود أسفل الطية، وشفافيته 0، بفضل CSS الأساسي (القسم 1).
- يُنزِّل المتصفح CSS غير الأساسي (القسم 3).
- يُكمل المتصفح تحليل HTML والأنماط، ثم يُشغِّل DOMContentLoaded.
- يُفعِّل جافا سكريبت لجعل المحتوى أسفل الطية مرئيًا (الشرح التوضيحي 4).
باختصار: يعمل المتصفح حتى CSS في أسفل الصفحة، حيث يعرض جميع نصوص HTML ويجعل محتوى أسفل الطية غير مرئي. بمجرد اكتمال ذلك، يتم تشغيل JavaScript لإظهار محتوى أسفل الطية.
باختصار: اعرض أعلى الطية بسرعة وأخفِ كل شيء آخر حتى يصبح جاهزًا.
خصص وقتًا لاستيعاب ما قيل هنا، والبناء عليه. أقترح عليك التخلي عن التوصيات التقليدية لـ React وAngular وآلاف التبعيات. بدلًا من ذلك، يجب عليك:
- تدقيق تبعياتك، وتقليلها إلى الحد الأدنى. طلبات أقل تعني تحميلًا أسرع للصفحة.
- فهم كيفية عمل علامات النصوص البرمجية، ومتى يتم تحميلها، وكيفية تجنب حجب محلل HTML.
- فهم متى يتم تحميل أوراق الأنماط – سواءً في الرأس أو الجسم – ثم فهم ما إذا كانت تحجب مُقدِّم HTML.
- اعرض نص HTML المرئي بأسرع ما يمكن؛ حمّل كل شيء في الوقت المناسب.
إذا كنت ترغب في تطبيق ما تعلمته للتو، ولكنك بحاجة إلى بعض الموارد لمساعدتك، فراجع المواقع الإلكترونية التي ينبغي على كل مطور ويب مبتدئ معرفتها. أو، إذا كنت ترغب في البدء ولكنك تفتقر إلى الإلهام، فراجع دليلنا حول كيفية إنشاء أول تطبيق ويب بسيط لعدّ الكلمات.
لا تحتاج إلى أدوات باهظة أو خوادم متطورة لتسريع موقعك. غالبًا ما يكون السبب في ملفات ضخمة غير مضغوطة، صور غير محسّنة، أو أكواد جافاسكربت غير مُدارة. باستخدام التعديلات البسيطة الموضحة في هذا المقال، يمكنك تقليل وقت تحميل صفحات موقعك بشكل ملحوظ. هل جرّبت واحدة من هذه الخطوات؟ شاركنا تجربتك أو اسأل في التعليقات لنساعدك في تحليل موقعك بشكل مجاني.