این روز ها که میشه با AI تقریبا هر کدی رو نوشت و خیلی ها از این ابزار برای نوشتن کد ها استفاده میکنن، ارزش کد نوشتن (صرفا نوشتن و تولید کد) داره کمتر میشه. تقریبا هرکسی میتونه هر اپلیکیشنی رو با React بنویسه. ولی فقط کد زدن تنها یه تیکه از این پازل هست. همچنان نیاز داریم که اپلیکیشن ها رو یه جایی Deploy کنیم، به کاربرا نمایشش بدیم، اون رو Maintainable تر کنیم، سرعتش رو بهبود بدیم و کلی کار دیگه که البته هنوز AI نمیتونه اینکارارو انجام بده. البته هنوز.
پس بیاید امروز، همونطور که از عنوان مطلب هم پیداست، روی "سریع" تر کردن اپلیکیشن هامون تمرکز کنیم. برای اینکه اینکارو انجام بدیم اول باید کلا React رو بذاریم کنار. چون قبل از اینکه بخوایم چیزی رو سریع تر کنیم، اول باید بفهمیم اصلا "سریع" یعنی چی، چطور اندازه گیریش کنیم و چه چیزهایی هستند که روی این "سریع تر" شدن تاثیر میذارن.
فقط قبلش اینو بگم: فعلا توی این قسمت خبری از React نیست! موضوع این مطلب فقط مبانی پایه ای و اصولی Performance هستن: اینکه چطور از ابراز های Performance استفاده کنیم، یه مقدمه ای بر Core Web Vitals (که احتمالا بار ها اسمش رو شنیدید)، ابزار های اندازه گیری توی Chrome، اینکه Initial Load Performance یعنی چی، چه شاخص (metric) هایی برای اندازه گیریشون داریم و چطور cache یا شرایط اینترنت (یا همون Network) روی اون میتونن تاثیر بذارن.

قبل از ادامه لطفا حتما توضیحات زیر رو بخونید:
منبع من توی این مطلب، نوشته ارزشمند Nadia هست که توی این لینک میتونید مطلب اصلی رو بخونید. خیلی دوست داشتم شرایط اجازه میداد که میشد کتابی که چند وقت پیش از همین نویسنده خوندم با عنوان Advanced React و آخرین کتابی که اخیرا (تیر ماه ۰۴) منتشر شده در مورد Performance رو ترجمه فارسی کنم و به صورت قانونی توی ایران برای همه در دسترس قرار بدم.
ولی بعد از صحبت با ایشون و البته محدودیت های Copyright توی ایران و تحریم و هزار چیز دیگه، نتیجه نهایی این شد که نوشته های ایشون توی وبسایت Developerway رو به صورت مقاله به فارسی منتشر کنم.
برای من که هر دو کتاب ها رو خوندم و مقاله ها رو هم نگاهی انداختم متوجه شدم که مطالب کتاب و مقاله های سایت باهم تفاوت چندان زیادی ندارند. در هر صورت این مطلب و قسمت های بعدی تا جای ممکن از نظر محتوا وفادار به منبع خواهند بود و صرفا من سعی کردم یه متن روان و راحت رو اینجا بنویسم.
ضمنا، برای ترجمه از هیچ ابزار AI استفاده نشده، برای اینکه خودمم بتونم نهایت استفاده رو از مطالب ببرم، تصمیم گرفتم خودم خط به خط اینکارو انجام بدم، گرچه برای امتحان کردن هم که شده سعی کردم از ChatGPT استفاده کنم ولی متن ترجمه اصلا جالب نبود و اثری از قلم خودم توش نبود، پس امیدوارم از ترجمه من لذت ببرید.
زمانی که ما مرورگر رو باز میکنیم و یه سایت رو باز میکنیم، دقیقا چه اتفاقی میوفته؟ فرضا مینویسیم http://www.my-website.com و مرورگر یه درخواست GET برای سرور ارسال میکنه و در جواب یه صفحه به صورت HTML برای ما برمیگرده. (توضیحات خیلی مفصل رو اینجا میتونید بخونید)

مدت زمانی که طول میکشه تا این اتفاق بیوفته رو بهش میگیم TTFB یا Time To First Byte. یعنی زمانی که مرورگر درخواست رو ارسال میکنه تا زمانی که جواب سرور شروع میکنه به جواب دادن. بعد اینکه HTML دریافت شد، حالا مرورگر باید اون رو به شکل قابل استفاده ای واسه کاربر نمایش بده. بنابراین مرورگر شروع میکنه به رندر کردن صفحه، فرایندی که بهش میگیم Critical Path. یعنی مهم ترین و مینیمال ترین حالتی از محتوا که بشه به کاربر نمایشش داد.

اینکه دقیقا چه چیزی باید توی Critical Path باشه سوال پیچیدهای هست. در حالت ایده آل، همه چیز هایی که کاربر بتونه به صورت کامل تجربه شون کنه ولی در عین حال، هم میخوایم که محتوا کمترین حد خودش باشه! چون نیاز داریم که در سریع ترین زمان ممکن این اتفاق بیوفته، چون که اصلا اسمش روشه: Critical Path (چیز های حیاتی یا واجب) پس نمیتونیم هم خر رو بخوایم و هم خرما رو. پس باید یه "بده بستون" داشته باشیم.
این "بده بستون" برای ما این شکلیه: مرورگر برای اینکه محتوا رو نمایش بده قطعا به این منابع احتیاج داره:
کد ها و المان های HTML اولیه که از سرور میاد (برای ساختن DOM)
کدهای مهم CSS که اون المان های HTML که قبلا گرفتیم رو شکل بدن - در صورت عدم وجودشون لحظات اول، کاربر ممکنه یه "فلش" از محتوای زشت و لخت HTML ببینه که یهو استایل ها بهشون اعمال میشه.
فایل های حیاتی JS که layout صفحه رو دستکاری (یا کنترل) میکنن.
توی اولی (HTML) که مرورگر توی اون درخواست اول از سمت سرور دریافت میکنه، شروع میکنه به Parse کردنش و همزمان با این اتفاق لینک هایی که به فایل های CSS و JS اشاره دارن که برای تکمیل Critical Path بهشون نیازه رو هم استخراج میکنه. بعد مجدد برای دریافت اون فایل ها (همون CSS و JS ها) درخواست میفرسته به سرور، صبر میکنه که دانلود بشن، پردازششون میکنه، ترکیب شون میکنه و در نهایت این پیکسل ها رو روی صفحه نشون میده! (تقریبا تمام مراحلی که توی این مقاله بهشون اشاره کردم قبلا)
از اونجایی که مرورگر نمیتونه رندر اولیه رو بدون اون فایل های CSS و JS انجام بده (چون فایل های حیاتی یا Critical هستند) ما اونا رو به عنوان منابع render-blocking میشناسیم. البته همه فایل های CSS و JS حالت render-blocking ندارند اونا فقط در زمانی به این حالت در میان که:
بیشتر CSS ها، چه اونایی که به صورت inline نوشته شدن و چه اونایی که توی تگ <link> هستن.
فایل های JS که توی تگ <head> باشن که حالت async یا defer نباشن.
به صورت کلی پس میتونیم کل فرایند Critical Path رو اینطور جمع بندی کنیم:
مرورگر HTML اولیه رو parse میکنه
همزمان با این اتفاق لینک های مرتبط با CSS و JS رو از تگ <head> استخراج میکنه
بعد شروع میکنه به دانلود کردن اون فایل ها (که بهشون میگفتیم render-blocking چون باید تا دانلودشون صبر کنه)
تو همین زمانی که ممکنه داشته باشه (در حین دانلود اون فایل ها) به parse کردن باقی HTML ها ادامه میده
بعد اینکه همه منابع حیاتی دریافت شد، پردازش انجام میشه
در نهایت همه کار های لازم برای اینکه در انتها پیکسل ها روی صفحه نقش ببندن انجام میشه و کار تمام
این مقطع از زمان رو بهش میگیم رسم (paint) اولیه یا همون First Paint یا FP. این اولین موقعی هست که کاربر میتونه یه چیزی روی صفحه ببینه. اینکه این اتفاق بیوفته یا نیوفته کاملا بستگی به HTML ای که سرور ارسال کرده داره. اگه چیز با معنایی اونجا باشه مثل یه متن یا تصویر، اینجا جایی میشه که بهش میگیم First Contentful Paint یا FCP. اگه HTML خالی باشه مثلا مثل یه div خالی، مرحله FCP بعدا اتفاق میتونه بیوفته.

مرحله First Contentful Paint یکی از مهم ترین شاخص ها (metric) برای اندازه گیری Performance یا عملکرد اپلیکیشن ماست. این نشون میده که سایت از نظر کاربر چقدر زود شروع به لود شدن کرده. یعنی اون حس اولیهی «سایت سریع باز شد یا نه» رو اندازه میگیره.
تا این لحظه، کاربرا فقط دارن به صفحهی خالی زل میزنن و معطل شدن! طبق گفتهی گوگل، یه عدد خوب برای FCP کمتر از ۱.۸ ثانیهست. اگه بیشتر از این طول بکشه، کاربرا کمکم علاقهشونو به اپلیکیشن از دست میدن و ممکنه صفحه رو ببندن و برن.
البته این معیار بهترین معیار نیست. مثلا اگه اپلیکیشن با یه اسپینر یا صفحهی لودینگ شروع بشه، همون به عنوان FCP ثبت میشه. ولی خب بعیده کسی وارد سایت بشه فقط برای دیدن یه صفحهی خوشگل لودینگ! بیشتر وقتا، کاربرا دنبال محتوا هستن، نه افکتهای نمایشی.
برای اینکه محتوا به کاربر نشون داده بشه، مرورگر باید کارایی که شروع کرده رو تموم کنه. یعنی باید JS هایی که اجرای اونها مانع بارگذاری نمیشن (همون non-blocking) رو هم اجرا کنه، تغییراتی که این کد ها ایجاد میکنن رو روی صفحه اعمال کنه (تغییرات مربوط به DOM)، عکسها رو دانلود کنه و خلاصه همه چیز رو ردیف کنه تا تجربه خوبی به کاربر بده.
یه جایی وسط این فرایند، زمان Largest Contentful Paint یا LCP اتفاق میافته. برخلاف FCP که فقط اولین چیز نشون داده شده رو بررسی میکنه، LCP تمرکزش روی بخش اصلی و بزرگترین محتوای صفحهست، مثل بزرگترین متن، عکس یا ویدیویی که توی صفحه دیده میشه.
طبق گفته گوگل این عدد بهتره زیر ۲.۵ ثانیه باشه. اگه بیشتر طول بکشه، کاربر فکر میکنه سایت کند داره لود میشه.

همهی این معیار (Metric) هایی که گفتیم، بخشی از Web Vitals گوگل هستن — یه سری معیار که تجربهی کاربر از یه صفحه رو اندازهگیری میکنن. LCP یکی از سه مورد اصلی توی Core Web Vitals حساب میشه — یعنی سه تا معیاری که هرکدوم یه بخش متفاوت از تجربهی کاربر رو پوشش میدن. LCP مسئول بخش «سرعت لود شدن محتوا» یا همون عملکرد بارگذاریه.
این معیارها رو میتونیم با Lighthouse اندازهگیری کنیم. Lighthouse یه ابزار بررسی عملکرد از طرف گوگله که توی Chrome DevTools هم به صورت built-in وجود داره، ولی میشه ازش با Shell Script یا UI یا به صورتNode Module هم استفاده کرد.
اگه بخوایم توی فرایند build پروژه ازش استفاده کنیم، میتونیم از node module اش استفاده کنیم که قبل از اینکه به مرحله Production برسه مشکلات اپلیکیشن رو ببینیم و فیکس کنیم.
برای دیباگ و تست لوکال، DevTools بهترین گزینهست. ولی اگه بخوایم عملکرد سایتهای رقیب رو بررسی کنیم، نسخهی وبش بهدرد میخوره.
همهی چیزایی که بالا گفتیم یه توضیح خیلی خلاصه و سادهشده از ماجرا بود. ولی همینم پر از اصطلاح و تئوریه که ممکنه آدمو گیج کنه! راستش برای من (نادیا) خوندن اینجور چیزا خیلی فایده نداره. چون اگه با چشم نبینم و باهاش بازی نکنم، همه چی از ذهنم میپره.
برای این موضوع خاص، بهترین راهی که من برای درک کاملش پیدا کردم اینه که سناریوهای مختلف رو روی یه پروژه نیمه واقعی شبیهسازی کنیم و ببینیم چطور نتیجه رو تغییر میدن. پس بیایم دقیقاً همین کار رو کنیم، قبل از اینکه بریم سراغ تئوریهای بیشتر (که واقعاً کلی چیز دیگه هم هست!).
ستاپ پروژه
میتونید همه شبیهسازی ها رو روی پروژه خودتون انجام بدین اگه دوس دارین، نتایج کم و بیش باید یکسان باشن. ولی برای سادگی و کنترل بیشتر من پیشنهاد میکنم که از این پروژه به صورت Case study استفاده کنیم. پروژه اینجا قابل دسترسه.
بعد اینکه پروژه رو کلون کردین به ترتیب دستورات رو اجرا کنید.
نصب dependency ها:
npm installبیلد پروژه:
npm run buildراه اندازی پروژه روی لوکال:
npm run startحالا باید روی لوکال http://localhost:3000 یه دشبورد خوشگل ببینید!

اپلیکیشنی که میخوایم تحلیلش کنیم رو توی Chrome باز میکنیم و ابزار Chrome DevTools رو باز میکنیم. بعد، پنلهای "Performance" و "Lighthouse" رو پیدا کنید و بیاریدشون کنار هم، چون به هر دوتاشون نیاز داریم. همچنین قبل از انجام هر کاری که توی این مقاله گفته شده، حتماً مطمئن شید که گزینهی "Disable cache" فعال باشه. با فعال کردنش، مطمئن میشیم که مرورگر کش قبلی رو استفاده نمیکنه و نتایج دقیقتری به دست میاد.

این کار برای اینه که بتونیم شرایط کاربرای تازه وارد رو شبیهسازی کنیم — یعنی کسایی که تا حالا وارد سایت ما نشدن و مرورگرشون هیچ فایل یا منابعی از قبل کش نکرده. با غیرفعال کردن کش، رفتار مرورگر مثل یه بازدید اول واقعی خواهد بود.
حالا پنل Lighthouse رو باز کنید. چندتا تنظیمات میبینیم و یه دکمه به اسم "Analyze page load".
تو این بخش، حالت "Navigation" برامون مهمه — این حالت یه تحلیل دقیق از بارگذاری اولیهی صفحه انجام میده.
بعد از اجرا، یه گزارش کامل بهت میده که شامل امتیازهایی مثل اینهاست:
Performance: عملکرد کلی بارگذاری
Accessibility: دسترسیپذیری صفحه
Best Practices: رعایت اصول کدنویسی و امنیت
SEO: بهینهسازی برای Search Engine ها
PWA (اگه فعال باشه): وضعیت اپلیکیشن در حالت PWA
این امتیازها بهت کمک میکنن بفهمی سایتت از نگاه کاربر چقدر سریع و درست لود میشه.

عملکرد سایت بهصورت لوکال عالیه، که تعجبی هم نداره — همه چیز همیشه روی سیستم خودمون «درست کار میکنه»! 😄
این معیار ها هم هستند:

مقادیر FCP و LCP که برای این مقاله نیاز داریم، دقیقاً در بالای گزارش دیده میشن.
کمی پایینتر، یه لیست از پیشنهادها میبینیم که میتونن بهمون کمک کنن امتیاز عملکرد رو بهتر کنیم — مثلاً کاهش حجم تصاویر، بهینهسازی فونتها، حذف جاوااسکریپتهای اضافی و غیره.

هر کدوم از این پیشنهادها رو میتونیم باز کنیم تا اطلاعات دقیقتری ببینیم — بعضی وقتا حتی لینکهایی داره که اون موضوع خاص رو کاملتر توضیح میدن. البته همهی این پیشنهادها قابل اجرا نیستن، ولی واقعاً ابزار فوقالعادهایه برای شروع بهینهسازی عملکرد اپلیکیشن و یاد گرفتن چیزهای مختلفی که میتونن روش تأثیر بذارن. میتونیم ساعتها فقط گزارشها و لینکهای مرتبطش رو بخونیم و کلی چیز یاد بگیریم!
با این حال، Lighthouse فقط اطلاعات سطحی در اختیارمون میذاره و اجازه نمیده سناریوهای مختلف مثل شبکه کند یا CPU ضعیف رو شبیهسازی کنیم.
این ابزار بیشتر یه نقطهی شروع خوبه و برای Track کردن تغییرات در طول زمان خیلی مفیده. اما اگه بخوایم دقیقتر بفهمیم چه اتفاقی داره میافته، باید سراغ پنل "Performance" بریم.
وقتی که این قسمت رو باز میکنیم یه همچین چیزی میبینیم:

این پنل سه تا معیار اصلی Core Web Vitals رو نشون میده — که یکی از اونها همون LCP مورد نظر ماست.
همچنین امکان شبیهسازی شبکهی کند و CPU ضعیف رو بهمون میده و میتونیم عملکرد صفحه رو به صورت لحظه به لحظه (Real-time) ضبط کنیم.
برای شروع، گزینهی "Screenshots" رو که بالای پنل قرار داره، تیک بزنید. بعدش روی دکمهی "Record and reload" کلیک کنید. وقتی صفحه خودش دوباره لود شد، ضبط رو متوقف کنید. این گزارش، جزئیات دقیقی از اتفاقاتی که حین لود اولیهی صفحه افتاده رو بهمون نشون میده.
این گزارش چند بخش داره:
در بالاترین قسمت Timeline Overview (نمای کلی زمانبندی) قراره گرفته.

اینجا میتونیم ببینیم که یه چیزی روی سایت در حال اتفاق افتادنه، اما چیز زیادی بیشتر از اون مشخص نیست. وقتی موس رو Hover کنیم، اسکرینشاتی از همون لحظه ظاهر میشه و میتونیم یه بازهی خاص رو انتخاب کنیم و روش زوم کنیم تا دقیقتر ببینیم چه خبره.
پایینش، یه بخشی به نام Network وجود داره. که وقتی بازش کنیم، تمام منابع خارجی (External Resources) که در حال دانلود هستن و زمان دقیقشون روی تایملاین رو میتونیم ببینیم. وقتی موس رو روی یه منبع خاص نگه داریم، اطلاعات دقیقی از اینکه در هر مرحله از دانلود چقدر زمان صرف شده نشون داده میشه. منابعی که گوشهی قرمز دارن، نشون دهندهی منابع بلاککننده (Blocking Resources) هستن.

اگه دارید با پروژهی آزمایشی کار میکنید، دقیقاً همین تصویر رو میبینید، و این تصویر مو به مو با چیزی که تو بخش قبلی گفتیم مطابقت داره:
در ابتدا، یه بلوک آبی دیده میشه — که مربوط به درخواست HTML صفحهست.
بعد از اینکه لود شدنش تموم شد، با یه مکث کوتاه (برای پردازش یا Parse کردن HTML)، دو تا درخواست دیگه برای منابع جدید ارسال میشه.
یکی از اونها (زرد رنگ) برای فایل JavaScript ـه — که بلاککننده نیست.
اون یکی (بنفش رنگ) برای CSS هست — و این یکی بلاککنندهست.
اگه الان کد پروژهی آزمایشی رو مثلا توی VSCode باز کنیم و یه نگاهی به پوشهی dist بندازیم، کد منبع دقیقاً با همین رفتار مطابقت داره:
یه فایل index.html وجود داره و داخل پوشهی assets هم فایلهای .css و .js قرار دارن
داخل فایل index.html و در بخش <head>، یه تگ <link> هست که به فایل CSS اشاره میکنه. همونطور که میدونیم، منابع CSS توی <head> باعث توقف رندر میشن، پس این مورد هم درست از آب دراومده.
همچنین داخل <head> یه تگ هست که به فایل جاوااسکریپت داخل پوشهی assets اشاره میکنه. این تگ نه defer داره و نه async، ولی نوعش type="module" هست. فایلهایی که به این صورت هستن بهصورت خودکار defer میشن، پس این مورد هم منطقیه — فایل جاوااسکریپت توی پنل، غیر بلاککننده نشون داده میشه.
تمرین اضافه
اگه یه پروژه واقعی دارید که روش کار میکنید، عملکرد بارگذاری اولیهش رو ضبط کنید و یه نگاهی به پنل Network بندازین. احتمال زیاد میبینید که منابع خیلی بیشتری دارن دانلود میشن.
چند تا Blocking Resource دارید؟ آیا همهی اونها واقعاً ضروریان؟
میدونی نقطهی شروع (entry) پروژه کجاست و این منابع بلاککننده چطور داخل تگ <head/> قرار میگیرن؟ یه بار پروژه تون رو با دستور npm build بیلد بگیرید و بعد دنبال این منابع بگردید تا ببینید از کجا اومدن. راهنمایی:
اگه پروژه تون بر پایهی Webpack باشه، دنبال فایل webpack.config.js بگردید — مسیرهای مربوط به ورودی HTML معمولاً اون تو هستن.
اگه از Vite استفاده میکنید، مثل همین پروژهی آزمایشی خودمون، یه نگاهی به پوشهی dist بندازید.
و اگه با Next.js و App Router کار میکنید، یه سر به پوشهی next/server/app. بزنید.
زیر بخش Network، میتونید قسمت Frames و Timing هم ببینیم.

این بخشها خیلی باحالن. توی بخش Timing میتونیم همهی اون معیار هایی که قبلاً گفتیم رو ببینیم (FP و FCP و LCP) بهعلاوهی چندتا متریک دیگه که هنوز نگفتیم. اگه موس رو روی هر کدوم ببریم، زمان دقیقش رو نشون میده. اگه روش کلیک کنیم، تب "Summary" که پایین صفحهست آپدیت میشه و اونجا میتونیم ببینیم این متریک دقیقاً چیه و یه لینک هم هست برای اطلاعات بیشتر.
نهایتا، بخش Main — اینجا دقیقاً نشون میده که روی main thread چه اتفاقهایی افتاده توی اون بازهای که ضبط شده.

اینجا میتونیم چیزهایی مثل "Parse HTML" یا "Layout" رو ببینیم و اینکه چقدر طول کشیدن. بخشهای زرد مربوط به جاوااسکریپت هستن، که خب خیلی دقیق نیستن چون داریم از نسخهی production با کد Minify شده استفاده میکنیم. ولی حتی توی همین حالت هم یه دید کلی بهمون میده که مثلاً اجرای جاوااسکریپت چقدر زمان میبره نسبت به پردازش HTML یا رسم Layout.
این بخش به ویژه وقتی خیلی کاربردیه که هم Network و هم Main باز باشن و زوم شده باشن تا کل صفحه رو پوشش بدن.

از اینجا میتونیم ببینم که سرور مون فوقالعاده سریعه و باندلها هم کوچیک و سریع هستن. هیچکدوم از تسکهای شبکه Bottleneck نیستن؛ زمان خاصی نمیگیرن و بینشون، مرورگر راحت نشسته و کار خودش رو میکنه. پس اگه بخوایم سرعت بارگذاری اولیه رو بیشتر کنیم، باید بررسی کنیم چرا Parse HTML اینقدر طول میکشه — این طولانیترین بخش توی نموداره.
یا اگه بخوایم از نظر عددی نگاه کنیم — اصلاً لازم نیست کاری کنیم! کل بارگذاری اولیه کمتر از ۲۰۰ میلیثانیه طول میکشه، که خیلی پایینتر از حد پیشنهادی گوگله 😁 ولی این به خاطر اینه که داریم این تست رو بهصورت لوکال انجام میدیم (بدون هزینههای واقعی شبکه)، روی یه لپتاپ خیلی سریع و با یه سرور ساده.
وقتشه شرایط واقعی رو شبیهسازی کنیم.
این قسمت اول از سلسله پست های مرتبط با Performance هست. همونطور که گفتم منبع اصلی این پست اینجاست:
که با اجازه خود نویسنده (نادیا) مطلب ترجمه داره میشه و داریم باهم یاد میگیریم. به خاطر طولانی بودن مطلب، تصمیم گرفتم توی یک مقاله همه اش رو نیارم چون خوندنش سخت میشه. اگه دوست داشتین میتونید منبع اصلی رو بخونید. در هر صورت امیدوارم از این مطلب چیزی یاد گرفته باشید.