در دنیای رقابتی طراحی وب امروز، سرعت و عملکرد وبسایت به یکی از مهمترین فاکتورهای موفقیت تبدیل شده است. بهینهسازی وردپرس برای دستیابی به امتیاز 100 در Lighthouse دیگر یک گزینه لوکس نیست، بلکه یک ضرورت حیاتی برای هر کسب و کار آنلاین محسوب میشود. گوگل با معرفی Core Web Vitals استانداردهای جدیدی را برای ارزیابی تجربه کاربری وبسایتها ارائه کرده که مستقیماً بر رتبهبندی سایتها در نتایج جستجو تأثیر میگذارد.
با توجه به اینکه بیش از 40% از وبسایتهای جهان با وردپرس ساخته شدهاند، بهینهسازی این سیستم مدیریت محتوا برای رعایت استانداردهای Core Web Vitals اهمیت ویژهای پیدا کرده است. صاحبان سایتهای وردپرسی باید بدانند که چگونه میتوانند سایت خود را برای دستیابی به حداکثر امتیاز در Lighthouse گوگل بهینه کنند.
در این راهنمای جامع از وردپرس نیاز که در هیچ کجای دنیای وب چنین مقاله و آموزش بهینه سازی را نمیبینید ، به صورت تخصصی و قدم به قدم تمام جنبههای بهینهسازی وردپرس برای دستیابی به امتیاز 100 در Core Web Vitals را بررسی خواهیم کرد. از آشنایی با مفاهیم پایه تا تکنیکهای پیشرفته، این مقاله منبعی کامل برای توسعهدهندگان، طراحان و مدیران وبسایتهای وردپرسی خواهد بود. پس با ما همراه باشید تا ببینیم چگونه میتوان تجربه کاربری در وردپرس را به سطح جدیدی ارتقا داد.
1. Core Web Vitals چیست و چرا برای سایت وردپرسی شما مهم است؟
Core Web Vitals مجموعهای از معیارهای استاندارد گوگل برای سنجش کیفیت تجربه کاربری در وبسایتها است که از سال 2020 به طور رسمی به عنوان یکی از فاکتورهای رتبهبندی در الگوریتمهای گوگل معرفی شد. این معیارها بر سه جنبه اصلی عملکرد سایت تمرکز دارند: سرعت بارگذاری، تعاملپذیری و ثبات بصری. وردپرس نیاز به توجه ویژهای در این زمینه دارد، زیرا ساختار پیچیدهای از قالبها، افزونهها و کدهای سفارشی میتواند بر این معیارها تأثیر منفی بگذارد.
اهمیت Core Web Vitals را میتوان در چند بخش کلیدی خلاصه کرد:
- تأثیر مستقیم بر سئو: گوگل به صراحت اعلام کرده که این معیارها در الگوریتم رتبهبندی نتایج جستجو تأثیرگذار هستند. سایتهایی با Core Web Vitals ضعیف، رتبههای پایینتری دریافت میکنند.
- بهبود تجربه کاربری: سایتهای سریع و واکنشگرا، نرخ تعامل و تبدیل بالاتری دارند. طبق آمار گوگل، افزایش زمان بارگذاری از 1 به 3 ثانیه، احتمال خروج کاربر را تا 32% افزایش میدهد.
- مزیت رقابتی: با بهینهسازی Core Web Vitals، میتوانید از رقبایی که به این معیارها توجه نکردهاند، پیشی بگیرید.
- سازگاری با موبایل: با توجه به اهمیت روزافزون جستجوی موبایلی، بهینهسازی Core Web Vitals برای نسخه موبایل وبسایتها حیاتی است.
اما چالشهای خاصی برای سایتهای وردپرسی وجود دارد. وردپرس نیاز به بهینهسازی در لایههای مختلف دارد – از انتخاب هاست مناسب گرفته تا مدیریت افزونهها و بهینهسازی کدها. به عنوان مثال، یک سایت وردپرسی معمولی ممکن است بیش از 50 درخواست HTTP برای بارگذاری کامل صفحه اصلی نیاز داشته باشد، در حالی که این رقم در سایتهای بهینه شده میتواند به کمتر از 20 درخواست کاهش یابد.
جدول مقایسهای تأثیر Core Web Vitals بر عملکرد سایت
معیار | سایت با Core Web Vitals ضعیف | سایت با Core Web Vitals بهینه |
---|---|---|
نرخ خروج کاربران | ۳۸٪ | ۲۲٪ |
میانگین زمان بازدید | ۲ دقیقه و ۱۰ ثانیه | ۳ دقیقه و ۴۰ ثانیه |
نرخ تبدیل | ۱.۸٪ | ۳.۲٪ |
رتبه متوسط در گوگل | صفحه ۲-۳ | صفحه ۱ |
برای درک بهتر اهمیت Core Web Vitals، کافی است بدانید که طبق گزارشهای گوگل، سایتهایی که معیارهای Core Web Vitals را برآورده میکنند، 24% کاهش در نرخ خروج کاربران و 25% افزایش در نرخ تبدیل را تجربه میکنند. این آمارها نشان میدهد که بهینهسازی این معیارها نه تنها برای سئوی تکنیکال وردپرس مهم است، بلکه مستقیماً بر درآمدزایی سایت نیز تأثیر میگذارد.
حال که با اهمیت Core Web Vitals آشنا شدیم، بیایید عمیقتر به بررسی هر یک از معیارهای اصلی آن بپردازیم تا درک بهتری از آنچه باید بهینهسازی کنیم، داشته باشیم.
2. آشنایی با معیارهای اصلی Core Web Vitals: LCP، FID و CLS
Core Web Vitals شامل سه معیار اصلی است که هر کدام جنبه مهمی از تجربه کاربری را اندازهگیری میکنند. درک دقیق این معیارها اولین قدم در مسیر بهینهسازی وردپرس برای دستیابی به امتیاز 100 در Lighthouse است. بیایید هر یک از این معیارها را به صورت تخصصی بررسی کنیم:
LCP (Largest Contentful Paint) چیست
LCP (Largest Contentful Paint) چیست؟ این معیار زمان لازم برای نمایش بزرگترین عنصر محتوایی در صفحه را اندازهگیری میکند. به عبارت سادهتر، LCP نشان میدهد کاربر چقدر باید منتظر بماند تا بخش اصلی محتوای صفحه (مانند یک تصویر بزرگ، ویدیو یا بلوک متنی) نمایش داده شود. این معیار یکی از مهمترین شاخصهای سرعت بارگذاری از دیدگاه کاربر است.
استانداردهای گوگل برای LCP:
- خوب: کمتر از 2.5 ثانیه
- نیاز به بهبود: بین 2.5 تا 4 ثانیه
- ضعیف: بیشتر از 4 ثانیه
عوامل اصلی تأثیرگذار بر LCP در سایتهای وردپرسی عبارتند از:
- زمان پاسخگویی سرور (TTFB)
- فایلهای CSS و JavaScript مسدودکننده رندر
- زمان بارگذاری منابع (به خصوص تصاویر)
- کیفیت هاست و پیکربندی سرور
FID (First Input Delay) چیست
FID (First Input Delay) چیست؟ این معیار زمان بین اولین تعامل کاربر با صفحه (مثلاً کلیک روی یک لینک) و زمانی که مرورگر به آن پاسخ میدهد را اندازهگیری میکند. FID معیاری برای سنجش تعاملپذیری صفحه است و نشان میدهد آیا سایت به اندازه کافی واکنشگرا است یا خیر.
استانداردهای گوگل برای FID:
- خوب: کمتر از 100 میلیثانیه
- نیاز به بهبود: بین 100 تا 300 میلیثانیه
- ضعیف: بیشتر از 300 میلیثانیه
مشکلات رایج FID در وردپرس شامل:
- اسکریپتهای سنگین جاوااسکریپت
- افزونههای متعدد که رویدادهای زیادی را در صفحه ثبت میکنند
- اجرای کدهای طولانی در main thread مرورگر
- عدم استفاده از تکنیکهای code-splitting و lazy-loading
CLS (Cumulative Layout Shift) چیست
CLS (Cumulative Layout Shift) چیست؟ این معیار میزان جابجایی ناخواسته عناصر صفحه در طول بارگذاری را اندازهگیری میکند. CLS زمانی رخ میدهد که عناصری مانند تصاویر، ویدیوها، فونتها یا تبلیغات بدون تعیین ابعاد مشخص بارگذاری میشوند و باعث میشوند سایر عناصر صفحه جابجا شوند.
استانداردهای گوگل برای CLS:
- خوب: کمتر از 0.1
- نیاز به بهبود: بین 0.1 تا 0.25
- ضعیف: بیشتر از 0.25
دلایل اصلی CLS بالا در سایتهای وردپرسی:
- تصاویر بدون ابعاد مشخص
- محتوای تزریق شده با جاوااسکریپت
- انیمیشنهای نامناسب
- فونتهای وب که باعث تغییر اندازه متن میشوند
- تبلیغات و ویجتهای شخص ثالث
برای درک بهتر این معیارها، به جدول مقایسهای زیر توجه کنید:
جدول ۲: مقایسه معیارهای Core Web Vitals
معیار | چه چیزی را میسنجد | تأثیر بر تجربه کاربری | چالشهای رایج در وردپرس |
---|---|---|---|
LCP | سرعت نمایش محتوای اصلی | احساس سرعت بارگذاری | تصاویر بزرگ، قالبهای سنگین، افزونههای متعدد |
FID | واکنشپذیری به تعامل کاربر | احساس پاسخگویی سایت | اسکریپتهای سنگین، افزونههای متعدد، انیمیشنهای پیچیده |
CLS | ثبات بصری صفحه | احساس پایداری و قابل اعتماد بودن | تصاویر بدون ابعاد، فونتهای وب، تبلیغات |
گوگل برای ارزیابی Core Web Vitals از دادههای واقعی کاربران (Real User Metrics یا RUM) استفاده میکند که از طریق Chrome User Experience Report (CrUX) جمعآوری میشوند. این به این معنی است که بهینهسازی باید در شرایط واقعی و برای کاربران واقعی مؤثر باشد، نه فقط در محیطهای آزمایشی.
برای بهینهسازی وردپرس با هدف بهبود Core Web Vitals، باید رویکردی جامع داشته باشیم که هر سه معیار را پوشش دهد. در ادامه این مقاله، به روشهای عملی و تکنیکهای پیشرفته برای بهبود هر یک از این معیارها خواهیم پرداخت. اما قبل از شروع بهینهسازی، باید بدانیم که وضعیت فعلی سایت ما چگونه است و برای این کار نیاز به ابزارهای مناسب داریم.
3. چگونه وضعیت فعلی Core Web Vitals سایت وردپرسی خود را بررسی کنیم؟
قبل از شروع فرآیند بهینهسازی وردپرس، ضروری است که وضعیت فعلی Core Web Vitals سایت خود را به درستی ارزیابی کنید. این مرحله به شما کمک میکند تا نقاط ضعف را شناسایی کرده و استراتژی بهینهسازی هدفمندی را تدوین نمایید. خوشبختانه، گوگل مجموعهای از ابزارهای قدرتمند برای این منظور ارائه کرده است.
PageSpeed Insights چیست
PageSpeed Insights چیست و چگونه میتواند به ما کمک کند؟ این ابزار رایگان گوگل، ترکیبی از دادههای آزمایشگاهی (Lighthouse) و دادههای واقعی کاربران (CrUX) را برای تحلیل عملکرد صفحات وب ارائه میدهد. برای استفاده از PageSpeed Insights، کافی است URL سایت خود را در PageSpeed Insights وارد کنید تا گزارش کاملی از وضعیت Core Web Vitals سایت شما نمایش داده شود.
مزایای استفاده از PageSpeed Insights:
- ارائه امتیاز عملکرد از 0 تا 100
- گزارش تفکیکی برای نسخههای دسکتاپ و موبایل
- پیشنهادات اختصاصی برای بهبود عملکرد
- نمایش دادههای واقعی کاربران در صورت وجود ترافیک کافی
جدول ارزیابی امتیاز Core Web Vitals
امتیاز | وضعیت | تفسیر |
---|---|---|
90-100 | عالی | سایت عملکرد بسیار خوبی دارد |
50-89 | نیاز به بهبود | فرصتهای بهبود وجود دارد |
0-49 | ضعیف | نیاز به بهینهسازی اساسی دارد |
گوگل Search Console
گوگل Search Console ابزار دیگری است که گزارشهای Core Web Vitals را برای کل سایت شما ارائه میدهد. این ابزار دادههای واقعی کاربران را در طول زمان جمعآوری میکند و به شما امکان میدهد روند بهبود را پیگیری کنید. برای دسترسی به این گزارشها:
- وارد حساب Search Console خود شوید
- از منوی سمت چپ، بخش “تجربه کاربری” را انتخاب کنید
- روی “Core Web Vitals” کلیک کنید
این گزارشها به شما نشان میدهند کدام صفحات مشکلات مشابهی دارند و میتوانید آنها را براساس اولویت مرتب کنید.
Lighthouse گوگل
Lighthouse گوگل یک ابزار آزمایشگاهی قدرتمند است که میتواند عملکرد، دسترسیپذیری، بهترین شیوهها، SEO و قابلیتهای پیشرفته وب را ارزیابی کند. برای استفاده از Lighthouse:
- در مرورگر Chrome، صفحه مورد نظر را باز کنید
- با فشردن F12 یا کلیک راست و انتخاب “Inspect”، DevTools را باز کنید
- به تب “Lighthouse” بروید
- گزینههای مورد نظر را انتخاب کرده و روی “Generate report” کلیک کنید
Lighthouse گزارش جامعی ارائه میدهد که شامل موارد زیر است:
- امتیاز عملکرد کلی
- زمانبندی دقیق بارگذاری صفحه
- فرصتهای بهبود با تخمین صرفهجویی در زمان
- تشخیص منابع مسدودکننده
Chrome DevTools
Chrome DevTools مجموعهای از ابزارهای توسعهدهنده است که در مرورگر Chrome تعبیه شده و امکانات پیشرفتهای برای تحلیل عملکرد ارائه میدهد:
- Performance Tab: امکان ضبط و تحلیل عملکرد صفحه را فراهم میکند
- Network Tab: تمام درخواستهای شبکه و زمانبندی آنها را نشان میدهد
- Coverage Tab: کدهای استفاده نشده CSS و JavaScript را شناسایی میکند
- Performance Insights: تحلیل خودکار Core Web Vitals را ارائه میدهد
یک تکنیک مفید برای شبیهسازی شرایط واقعی کاربران، استفاده از قابلیت “Throttling” در Chrome DevTools است. این قابلیت به شما امکان میدهد شرایط اتصال اینترنت کندتر یا دستگاههای ضعیفتر را شبیهسازی کنید.
ابزارهای تخصصی وردپرس
علاوه بر ابزارهای عمومی گوگل، افزونههای تخصصی وردپرس نیز وجود دارند که میتوانند به شما در ارزیابی و بهبود Core Web Vitals کمک کنند:
- Query Monitor: برای تحلیل کوئریهای دیتابیس و شناسایی کوئریهای کند
- Perfmatters: برای بهینهسازی عملکرد و غیرفعال کردن ویژگیهای غیرضروری
- WP Rocket: ارائه گزارشهای عملکرد همراه با راهکارهای بهبود
- Hummingbird: تحلیل عملکرد و پیشنهاد راهکارهای بهینهسازی
برای یک ارزیابی کامل، توصیه میشود از ترکیبی از این ابزارها استفاده کنید. هر ابزار دیدگاه متفاوتی ارائه میدهد که میتواند به شما کمک کند تصویر جامعتری از وضعیت Core Web Vitals سایت خود داشته باشید.
نکات کلیدی برای تحلیل موثر Core Web Vitals:
• همیشه هم نسخه موبایل و هم دسکتاپ را بررسی کنید
• چندین صفحه مختلف (صفحه اصلی، صفحات محصول، مقالات) را تحلیل کنید
• آزمایشها را چندین بار تکرار کنید تا نتایج دقیقتری به دست آورید
• به روند تغییرات در طول زمان توجه کنید
• مشکلات را براساس تأثیر و سهولت رفع اولویتبندی کنید
پس از ارزیابی دقیق وضعیت فعلی سایت، میتوانید با درک بهتری از چالشهای پیش رو، به سراغ استراتژیهای بهینهسازی بروید. در بخش بعدی، به بررسی تکنیکهای کاربردی برای بهبود LCP خواهیم پرداخت که یکی از مهمترین معیارهای Core Web Vitals است.
4. استراتژی جامع بهینهسازی LCP در وردپرس
LCP (Largest Contentful Paint) چیست یکی از مهمترین معیارهای Core Web Vitals است که مستقیماً بر احساس سرعت کاربران تأثیر میگذارد. بهبود LCP در سایتهای وردپرس نیاز به رویکردی چندلایه دارد، زیرا عوامل متعددی از سطح سرور تا کدهای فرانتاند میتوانند بر آن تأثیر بگذارند. در این بخش، استراتژیهای تخصصی برای بهینهسازی LCP را بررسی میکنیم.
بهینهسازی TTFB (Time To First Byte)
TTFB (Time To First Byte) نقطه شروع تمام فرآیندهای بارگذاری است و تأثیر مستقیمی بر LCP دارد. برای بهبود TTFB در وردپرس:
- ارتقای هاست و سرور: انتخاب هاست مناسب با منابع کافی، اولین و مهمترین گام است. هاستهای مدیریت شده وردپرس معمولاً عملکرد بهتری نسبت به هاستهای اشتراکی عمومی دارند.
- پیادهسازی کش سرور: استفاده از سیستمهای کش در سطح سرور مانند Redis یا Memcached میتواند TTFB را به طور چشمگیری کاهش دهد. این کد را میتوان به فایل wp-config.php اضافه کرد:
PHPdefine('WP_CACHE', true);
- بهینهسازی دیتابیس: اجرای منظم عملیات بهینهسازی دیتابیس، حذف دادههای زائد و استفاده از ایندکسهای مناسب میتواند زمان پاسخگویی را کاهش دهد. میتوانید از افزونههایی مانند WP-Optimize استفاده کنید یا این کوئری را مستقیماً اجرا کنید:
SQLOPTIMIZE TABLE wp_posts, wp_postmeta, wp_options;
- استفاده از CDN: شبکههای توزیع محتوا میتوانند TTFB را با نزدیک کردن محتوا به کاربر نهایی کاهش دهند. CDNهای معتبر برای وردپرس شامل Cloudflare، StackPath و BunnyCDN هستند.
بهینهسازی CSS و JavaScript برای LCP
فایلهای CSS و JavaScript مسدودکننده رندر، یکی از رایجترین دلایل LCP ضعیف هستند:
- حذف CSS مسدودکننده رندر: استفاده از تکنیک Critical CSS برای بارگذاری فوری استایلهای ضروری و به تعویق انداختن بقیه. این کد میتواند به هدر سایت اضافه شود:
PHPfunction add_critical_css() {
echo '<style id="critical-css">' . file_get_contents(get_template_directory() . '/assets/css/critical.css') . '</style>';
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/css/main.css" as="style" onload="this.onload=null;this.rel=\'stylesheet\'">';
echo '<noscript><link rel="stylesheet" href="' . get_template_directory_uri() . '/assets/css/main.css"></noscript>';
}
add_action('wp_head', 'add_critical_css', 1);
- به تعویق انداختن JavaScript غیرضروری: اسکریپتهای غیرضروری را با استفاده از defer یا async بارگذاری کنید:
PHPfunction defer_parsing_of_js($url) {
if (is_admin()) return $url;
if (strpos($url, 'jquery.js')) return $url;
return str_replace(' src', ' defer src', $url);
}
add_filter('script_loader_tag', 'defer_parsing_of_js', 10);
- مینیفای و ترکیب فایلها: استفاده از بهینهسازی CSS و JavaScript برای کاهش حجم و تعداد درخواستها. افزونههایی مانند Autoptimize این کار را به خوبی انجام میدهند.
بهینهسازی بارگذاری محتوا
عنصر محتوایی بزرگ (Largest Contentful Paint) معمولاً یک تصویر، ویدیو یا بلوک متنی بزرگ است. برای بهینهسازی بارگذاری این عناصر:
- پیشبارگذاری منابع کلیدی: استفاده از تگ preload برای منابع مهم:
PHPfunction preload_largest_element() {
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/images/hero.webp" as="image">';
}
add_action('wp_head', 'preload_largest_element', 1);
- استفاده از تکنیکهای lazy-loading هوشمند: Lazy loading را فقط برای تصاویر زیر fold اعمال کنید، نه برای تصاویر بالای صفحه که میتوانند LCP باشند:
PHPfunction add_lazy_loading($content) {
return preg_replace_callback('/<img(.*?)src="(.*?)"(.*?)>/', function($matches) {
// اگر تصویر در هدر سایت است، lazy-loading را اعمال نکن
if (strpos($matches[0], 'header-image') !== false) {
return $matches[0];
}
return '<img' . $matches[1] . 'src="' . $matches[2] . '"' . $matches[3] . ' loading="lazy">';
}, $content);
}
add_filter('the_content', 'add_lazy_loading');
- بهینهسازی فونتها: استفاده از
font-display: swap
و preload برای فونتهای ضروری:
CSS@font-face {
font-family: 'Primary Font';
src: url('font.woff2') format('woff2');
font-display: swap;
}
استفاده از افزونههای تخصصی وردپرس
افزونههای زیر میتوانند به طور خاص به بهبود LCP کمک کنند:
- WP Rocket: یک افزونه جامع کش که بسیاری از بهینهسازیهای مورد نیاز برای LCP را به صورت خودکار انجام میدهد
- Flying Pages: پیشبارگذاری صفحات بعدی که کاربر احتمالاً به آنها مراجعه خواهد کرد
- EWWW Image Optimizer: بهینهسازی خودکار تصاویر بدون افت کیفیت قابل توجه
اندازهگیری و تحلیل پیشرفت
پس از اعمال این تغییرات، مهم است که تأثیر آنها را بر LCP اندازهگیری کنید:
- از ابزارهای مانیتورینگ مداوم مانند SpeedCurve یا New Relic استفاده کنید
- تستها را از مناطق جغرافیایی مختلف انجام دهید
- عملکرد را روی دستگاههای مختلف (به خصوص موبایل) بررسی کنید
جدول ۴: تأثیر تکنیکهای مختلف بر بهبود LCP
پیچیدگی پیادهسازی | تکنیک LCP | تأثیر متوسط بر LCP |
---|---|---|
متوسط | بهبود هاست | 30-50% |
متوسط | کش سرور | 20-40% |
کم | CDN | 15-30% |
زیاد | Critical CSS | 20-35% |
کم | به تعویق انداختن JavaScript | 10-25% |
کم | بارگذاری منابع کلیدی | 10-20% |
کم | بهینهسازی تصاویر | 15-30% |
متوسط | بهینهسازی دیتابیس | 5-15% |
یکی از چالشهای خاص در بهینهسازی وردپرس برای LCP، تداخل افزونههای مختلف است. گاهی افزونههای بهینهسازی میتوانند با یکدیگر تداخل داشته باشند. به عنوان مثال، اگر هم از یک افزونه کش و هم از یک افزونه بهینهسازی CSS/JS استفاده میکنید، ممکن است تنظیمات آنها با هم در تضاد باشند. در چنین مواردی، بهتر است از یک راهحل جامع مانند WP Rocket استفاده کنید یا تنظیمات افزونهها را به دقت بررسی کنید.
بهبود LCP یکی از موثرترین راهها برای افزایش امتیاز 100 در Lighthouse است، اما فقط یکی از سه معیار اصلی Core Web Vitals است. در بخش بعدی، به یکی از مهمترین جنبههای بهبود LCP، یعنی بهینهسازی تصاویر در وردپرس خواهیم پرداخت.
5. بهینهسازی تصاویر در وردپرس برای بهبود Core Web Vitals
بهینهسازی تصاویر وردپرس یکی از موثرترین راههای بهبود Core Web Vitals است، زیرا تصاویر معمولاً بزرگترین عناصر صفحه هستند و تأثیر قابل توجهی بر LCP و CLS دارند. در سایتهای وردپرسی، تصاویر میتوانند تا 60% از حجم کل صفحه را تشکیل دهند، بنابراین بهینهسازی تصاویر وردپرس یک اولویت اساسی است.
استفاده از فرمتهای مدرن تصویر
فرمتهای جدید تصویر مانند WebP، AVIF و JPEG 2000 میتوانند فشردهسازی بهتری نسبت به فرمتهای سنتی مانند JPEG و PNG ارائه دهند:
- تبدیل خودکار به WebP: میتوانید با افزودن این کد به فایل functions.php، تصاویر آپلود شده را به صورت خودکار به WebP تبدیل کنید:
PHPfunction convert_to_webp($file) {
if (!function_exists('imagewebp')) return $file;
$info = pathinfo($file['file']);
$dir = $info['dirname'];
$ext = $info['extension'];
if (!in_array($ext, ['jpg', 'jpeg', 'png'])) return $file;
$webp_file = $dir . '/' . $info['filename'] . '.webp';
if ($ext == 'png') {
$image = imagecreatefrompng($file['file']);
imagepalettetotruecolor($image);
imagealphablending($image, true);
imagesavealpha($image, true);
} else {
$image = imagecreatefromjpeg($file['file']);
}
imagewebp($image, $webp_file, 80);
imagedestroy($image);
// اضافه کردن فایل WebP به آرایه sizes
$file['sizes']['webp'] = [
'file' => basename($webp_file),
'width' => $file['width'],
'height' => $file['height'],
'mime-type' => 'image/webp'
];
return $file;
}
add_filter('wp_generate_attachment_metadata', 'convert_to_webp');
- ارائه تصاویر WebP با پشتیبانی از مرورگرهای قدیمی: با استفاده از تگ
<picture>
میتوانید هم WebP و هم فرمتهای سنتی را ارائه دهید:
PHPfunction replace_images_with_picture($content) {
return preg_replace_callback('/<img(.*?)src="(.*?)\.(jpg|jpeg|png)"(.*?)>/', function($matches) {
$webp_url = $matches[2] . '.webp';
$original_url = $matches[2] . '.' . $matches[3];
return '<picture>
<source srcset="' . $webp_url . '" type="image/webp">
<img' . $matches[1] . 'src="' . $original_url . '"' . $matches[4] . '>
</picture>';
}, $content);
}
add_filter('the_content', 'replace_images_with_picture');
پیادهسازی هوشمند Lazy Loading
Lazy loading یک تکنیک ضروری برای بهینهسازی است، اما باید به درستی پیادهسازی شود تا LCP را تحت تأثیر قرار ندهد:
- Lazy loading بومی: وردپرس از نسخه 5.5 به بعد، به صورت پیشفرض lazy loading را پشتیبانی میکند. برای مدیریت دقیقتر آن:
PHP// غیرفعال کردن lazy loading پیشفرض وردپرس
add_filter('wp_lazy_loading_enabled', '__return_false');
// پیادهسازی سفارشی lazy loading
function custom_lazy_loading($content) {
// شناسایی تصویر هدر یا تصویر اصلی
if (is_single() || is_page()) {
// تصویر اول را بدون lazy loading نگه دار (احتمالاً LCP است)
$content = preg_replace('/<img(.*?)class="(.*?)"/i', '<img$1class="$2 no-lazy"', $content, 1);
}
// اعمال lazy loading برای سایر تصاویر
$content = preg_replace('/<img((?!class=.*?no-lazy).*?)>/i', '<img$1 loading="lazy">', $content);
return $content;
}
add_filter('the_content', 'custom_lazy_loading');
- استفاده از Intersection Observer API: برای پیادهسازی lazy loading پیشرفتهتر:
JAVASCRIPTdocument.addEventListener("DOMContentLoaded", function() {
var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
if(lazyImage.dataset.srcset) {
lazyImage.srcset = lazyImage.dataset.srcset;
}
lazyImage.classList.remove("lazy");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}
});
تعیین ابعاد دقیق تصاویر برای جلوگیری از CLS
یکی از دلایل اصلی CLS بالا، عدم تعیین ابعاد تصاویر است. برای حل این مشکل:
- اطمینان از وجود width و height در تگهای img:
PHPfunction add_image_dimensions($content) {
return preg_replace_callback('/<img(.*?)>/', function($matches) {
$img_tag = $matches[0];
// اگر width و height وجود دارند، تگ را تغییر نده
if (strpos($img_tag, 'width=') !== false && strpos($img_tag, 'height=') !== false) {
return $img_tag;
}
// استخراج URL تصویر
preg_match('/src="([^"]*)"/', $img_tag, $src_match);
if (empty($src_match[1])) return $img_tag;
$src = $src_match[1];
$upload_dir = wp_upload_dir();
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $src);
if (file_exists($file_path)) {
list($width, $height) = getimagesize($file_path);
$img_tag = str_replace('<img', '<img width="' . $width . '" height="' . $height . '"', $img_tag);
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'add_image_dimensions');
- استف اده از CSS برای حفظ نسبت ابعاد:
CSS.wp-block-image img,
.entry-content img {
aspect-ratio: attr(width) / attr(height);
}
بهینهسازی پیشرفته تصاویر
تکنیکهای پیشرفتهتر برای بهینهسازی تصاویر شامل:
- ایجاد سایزهای سفارشی تصویر: برای ارائه تصاویر با اندازه مناسب برای هر نمایشگر:
PHPadd_image_size('mobile-hero', 576, 324, true);
add_image_size('tablet-hero', 768, 432, true);
add_image_size('desktop-hero', 1200, 675, true);
function responsive_image_markup($attachment_id, $classes = '') {
$image_meta = wp_get_attachment_metadata($attachment_id);
$width = $image_meta['width'];
$height = $image_meta['height'];
$mobile = wp_get_attachment_image_src($attachment_id, 'mobile-hero');
$tablet = wp_get_attachment_image_src($attachment_id, 'tablet-hero');
$desktop = wp_get_attachment_image_src($attachment_id, 'desktop-hero');
$full = wp_get_attachment_image_src($attachment_id, 'full');
$srcset = "{$mobile[0]} 576w, {$tablet[0]} 768w, {$desktop[0]} 1200w, {$full[0]} {$width}w";
$sizes = "(max-width: 576px) 576px, (max-width: 768px) 768px, (max-width: 1200px) 1200px, {$width}px";
return '<img src="' . $desktop[0] . '"
srcset="' . $srcset . '"
sizes="' . $sizes . '"
width="' . $width . '"
height="' . $height . '"
alt="' . get_post_meta($attachment_id, '_wp_attachment_image_alt', true) . '"
class="' . $classes . '">';
}
- فشردهسازی پیشرفته با حفظ کیفیت: میتوانید از کتابخانههای پیشرفته مانند MozJPEG برای فشردهسازی بیشتر با حفظ کیفیت استفاده کنید:
PHPfunction compress_jpeg_with_mozjpeg($file_path) {
if (!file_exists($file_path)) return false;
$output = shell_exec("mozjpeg -outfile " . escapeshellarg($file_path . '.optimized') . " -optimize -progressive " . escapeshellarg($file_path));
if (file_exists($file_path . '.optimized')) {
rename($file_path . '.optimized', $file_path);
return true;
}
return false;
}
- تصاویر SVG برای آیکونها و گرافیکهای ساده: SVGها حجم کمتری دارند و در هر رزولوشنی واضح هستند:
PHP// اجازه آپلود SVG در وردپرس
function allow_svg_upload($mimes) {
$mimes['svg'] = 'image/svg+xml';
return $mimes;
}
add_filter('upload_mimes', 'allow_svg_upload');
// امنیت SVG
function sanitize_svg($file) {
if ($file['type'] === 'image/svg+xml') {
$file_content = file_get_contents($file['tmp_name']);
$sanitized = preg_replace('/script|javascript|onclick|onerror|onload/i', 'sanitized', $file_content);
file_put_contents($file['tmp_name'], $sanitized);
}
return $file;
}
add_filter('wp_handle_upload_prefilter', 'sanitize_svg');
افزونههای مفید برای بهینهسازی تصاویر
برخی از بهترین افزونههای بهینهسازی تصاویر وردپرس عبارتند از:
- ShortPixel: فشردهسازی هوشمند تصاویر با حفظ کیفیت
- EWWW Image Optimizer: بهینهسازی خودکار تصاویر هنگام آپلود
- Imagify: فشردهسازی تصاویر با سه سطح مختلف کیفیت
- Smush: بهینهسازی تصاویر بدون کاهش قابل توجه کیفیت
- WebP Express: تبدیل خودکار تصاویر به فرمت WebP
مقایسه افزونههای بهینهسازی تصویر
افزونه | درصد متوسط کاهش حجم | پشتیبانی از WebP | API | رایگان ماهانه | قیمت نسخه حرفهای |
---|---|---|---|---|---|
ShortPixel | 50-80٪ | بله | 100 تصویر از $4.99/ماه | بله | – |
EWWW | 40-70٪ | بله | نامحدود (سرور شما) | بله | از $0/ماه |
Imagify | 60-80٪ | بله | 20MB | از $4.99/ماه | – |
Smush | 30-60٪ | بله | نامحدود | فقط در نسخه Pro | از $7.50/ماه |
WebP Express | فقط تبدیل فرمت | بله | نامحدود (سرور شما) | بله | رایگان |
بهینهسازی تصاویر وردپرس یک فرآیند مداوم است و باید به عنوان بخشی از جریان کاری منظم سایت شما در نظر گرفته شود. با پیادهسازی این تکنیکها، میتوانید حجم تصاویر را تا 70% کاهش دهید، در حالی که کیفیت بصری قابل قبولی حفظ میشود. این کاهش حجم به طور مستقیم بر LCP تأثیر میگذارد و میتواند زمان بارگذاری را تا 40% کاهش دهد.
در بخش بعدی، به بررسی یکی دیگر از عوامل کلیدی در بهینهسازی وردپرس خواهیم پرداخت: مدیریت و بهینهسازی فایلهای CSS و JavaScript.
6. حذف و مدیریت فایلهای CSS و JavaScript مانعساز در وردپرس
بهینهسازی CSS و JavaScript یکی از مهمترین جنبههای دستیابی به امتیاز 100 در Lighthouse است. در سایتهای وردپرسی، تعدد افزونهها و قالبها میتواند منجر به بارگذاری دهها فایل CSS و JavaScript شود که بسیاری از آنها ممکن است غیرضروری باشند یا به شکلی بارگذاری شوند که مانع رندر سریع صفحه شوند. در این بخش، تکنیکهای پیشرفته برای شناسایی، مدیریت و بهینهسازی این فایلها را بررسی میکنیم.
شناسایی فایلهای CSS و JavaScript مانعساز
قبل از هر اقدامی، باید فایلهای مشکلساز را شناسایی کنید:
- استفاده از Coverage در Chrome DevTools: این ابزار به شما نشان میدهد کدام بخشهای CSS و JavaScript واقعاً استفاده میشوند:
- F12 را فشار دهید تا DevTools باز شود
- Ctrl+Shift+P را فشار دهید و “Show Coverage” را تایپ کنید
- دکمه Record را فشار دهید و با صفحه تعامل کنید
- کدهای استفاده نشده با رنگ قرمز نشان داده میشوند
- شناسایی اسکریپتهای مسدودکننده رندر با Lighthouse: گزارش Lighthouse، فایلهایی که بارگذاری صفحه را به تأخیر میاندازند، شناسایی میکند.
- بررسی افزونههای مشکلساز: با استفاده از افزونه Query Monitor میتوانید ببینید هر افزونه چه فایلهایی را اضافه میکند:
PHP// افزودن این کد به functions.php برای شناسایی منشأ اسکریپتها
function track_script_origins() {
if (!is_admin() && current_user_can('administrator')) {
add_action('wp_enqueue_scripts', function() {
global $wp_scripts;
echo '<!-- Script Origins -->';
foreach ($wp_scripts->queue as $handle) {
$script = $wp_scripts->registered[$handle];
echo '<!-- ' . $handle . ': ' . $script->src . ' -->';
}
echo '<!-- End Script Origins -->';
}, 999);
}
}
add_action('init', 'track_script_origins');
حذف و مدیریت CSS غیرضروری
- حذف استایلهای غیرضروری: این کد به شما امکان میدهد استایلهای خاصی را از بارگذاری جلوگیری کنید:
PHPfunction dequeue_unnecessary_styles() {
// حذف استایلهای غیرضروری افزونهها
wp_dequeue_style('contact-form-7');
wp_dequeue_style('woocommerce-general');
// حذف استایلهای غیرضروری فقط در صفحات خاص
if (!is_woocommerce() && !is_cart() && !is_checkout()) {
wp_dequeue_style('woocommerce-layout');
wp_dequeue_style('woocommerce-smallscreen');
}
// حذف emoji
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
}
add_action('wp_enqueue_scripts', 'dequeue_unnecessary_styles', 100);
- پیادهسازی Critical CSS: استایلهای ضروری برای محتوای بالای صفحه را به صورت inline بارگذاری کنید:
PHPfunction add_critical_css() {
// تشخیص نوع صفحه
$critical_css_file = '';
if (is_home() || is_front_page()) {
$critical_css_file = 'home-critical.css';
} elseif (is_single()) {
$critical_css_file = 'single-critical.css';
} elseif (is_page()) {
$critical_css_file = 'page-critical.css';
} else {
$critical_css_file = 'global-critical.css';
}
$critical_css_path = get_template_directory() . '/assets/css/' . $critical_css_file;
if (file_exists($critical_css_path)) {
echo '<style id="critical-css">' . file_get_contents($critical_css_path) . '</style>';
}
}
add_action('wp_head', 'add_critical_css', 1);
- به تعویق انداختن CSS غیرضروری:
PHPfunction defer_non_critical_css() {
// لیست استایلهای غیرضروری
$non_critical_styles = array('theme-styles', 'plugin-styles');
global $wp_styles;
foreach ($non_critical_styles as $handle) {
if (isset($wp_styles->registered[$handle])) {
$style = $wp_styles->registered[$handle];
$wp_styles->add_data($handle, 'media', 'print');
echo '<noscript><link rel="stylesheet" href="' . $style->src . '"></noscript>';
echo '<link rel="preload" href="' . $style->src . '" as="style" onload="this.onload=null;this.media=\'all\'">';
}
}
}
add_action('wp_head', 'defer_non_critical_css', 99);
بهینهسازی بارگذاری JavaScript
- به تعویق انداختن اسکریپتهای غیرضروری با استفاده از defer و async:
PHPfunction defer_parsing_of_js($url) {
if (is_admin()) return $url;
// اسکریپتهایی که نباید به تعویق بیفتند
$do_not_defer = array('jquery.js', 'jquery.min.js');
if (strpos($url, '.js') === false) return $url;
foreach ($do_not_defer as $script) {
if (strpos($url, $script) !== false) return $url;
}
// اسکریپتهای تعاملی با async بارگذاری شوند
$async_scripts = array('comment-reply.js', 'contact-form.js');
foreach ($async_scripts as $script) {
if (strpos($url, $script) !== false) {
return str_replace(' src', ' async src', $url);
}
}
// سایر اسکریپتها با defer بارگذاری شوند
return str_replace(' src', ' defer src', $url);
}
add_filter('script_loader_tag', 'defer_parsing_of_js', 10, 1);
- تقسیم کد (Code Splitting) برای بارگذاری هوشمند:
JAVASCRIPT// نمونه کد برای تقسیم بارگذاری اسکریپتها در وردپرس
document.addEventListener('DOMContentLoaded', function() {
// بارگذاری اسکریپتهای مربوط به فرم تماس فقط وقتی کاربر به آن بخش اسکرول میکند
const contactForm = document.querySelector('.contact-form');
if (contactForm) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const script = document.createElement('script');
script.src = '/wp-content/themes/your-theme/assets/js/contact-form.js';
document.body.appendChild(script);
observer.unobserve(entry.target);
}
});
});
observer.observe(contactForm);
}
// بارگذاری اسکریپتهای مربوط به اسلایدر فقط در صورت وجود اسلایدر
const slider = document.querySelector('.slider');
if (slider) {
const script = document.createElement('script');
script.src = '/wp-content/themes/your-theme/assets/js/slider.js';
document.body.appendChild(script);
}
});
- استفاده از Service Workers برای کش و بارگذاری هوشمند:
PHPfunction register_service_worker() {
if (!is_admin()) {
echo '<script>
if ("serviceWorker" in navigator) {
window.addEventListener("load", function() {
navigator.serviceWorker.register("/sw.js").then(
function(registration) {
console.log("Service Worker registered with scope:", registration.scope);
},
function(error) {
console.log("Service Worker registration failed:", error);
}
);
});
}
</script>';
}
}
add_action('wp_head', 'register_service_worker');
مینیفای و ترکیب فایلها
- مینیفای کردن CSS و JavaScript: حذف فضاهای خالی، کامنتها و کاراکترهای غیرضروری:
PHPfunction minify_html($buffer) {
if (!is_admin() && !is_customize_preview()) {
// حذف کامنتها (به جز کامنتهای IE)
$buffer = preg_replace('/<!--(?!\s*(?:\[if [^\]]+]|<!|>))(?:(?!-->).)*-->/s', '', $buffer);
// حذف فضاهای خالی
$buffer = preg_replace('/\s+/', ' ', $buffer);
// حذف فضاهای خالی بین تگها
$buffer = preg_replace('/>\s+</', '><', $buffer);
return $buffer;
}
return $buffer;
}
function buffer_start() {
ob_start('minify_html');
}
function buffer_end() {
if (ob_get_length()) ob_end_flush();
}
add_action('wp_loaded', 'buffer_start');
add_action('shutdown', 'buffer_end');
- ترکیب فایلهای CSS و JavaScript: ادغام چندین فایل برای کاهش تعداد درخواستها:
PHPfunction combine_css_files() {
// لیست فایلهای CSS برای ترکیب
$css_files = array(
get_template_directory() . '/assets/css/base.css',
get_template_directory() . '/assets/css/layout.css',
get_template_directory() . '/assets/css/components.css'
);
$combined_css = '';
foreach ($css_files as $file) {
if (file_exists($file)) {
$combined_css .= file_get_contents($file);
}
}
// مینیفای کردن CSS
$combined_css = preg_replace('/\s+/', ' ', $combined_css);
$combined_css = preg_replace('/\/\*(?:(?!\*\/).)*\*\//', '', $combined_css);
$combined_css = str_replace(': ', ':', $combined_css);
$combined_css = str_replace('{ ', '{', $combined_css);
$combined_css = str_replace(' }', '}', $combined_css);
$combined_css = str_replace('; ', ';', $combined_css);
// ذخیره فایل ترکیبی
$combined_file = get_template_directory() . '/assets/css/combined.min.css';
file_put_contents($combined_file, $combined_css);
// ثبت و بارگذاری فایل ترکیبی
wp_register_style('combined-styles', get_template_directory_uri() . '/assets/css/combined.min.css', array(), filemtime($combined_file));
wp_enqueue_style('combined-styles');
// حذف فایلهای اصلی
global $wp_styles;
foreach (array('base-style', 'layout-style', 'components-style') as $handle) {
wp_dequeue_style($handle);
}
}
add_action('wp_enqueue_scripts', 'combine_css_files', 999);
استفاده از افزونههای بهینهسازی
برخی از بهترین افزونههای بهینهسازی CSS و JavaScript در وردپرس عبارتند از:
- Autoptimize: ترکیب، مینیفای و کش کردن CSS و JavaScript
- WP Rocket: بهینهسازی جامع شامل تأخیر در بارگذاری JavaScript
- Fast Velocity Minify: ترکیب و مینیفای هوشمند با پشتیبانی از مسیرهای نسبی
- Async JavaScript: کنترل دقیق بر نحوه بارگذاری اسکریپتها
- Perfmatters: کنترل دقیق بر اسکریپتها و استایلها در هر صفحه
مقایسه روشهای مختلف بهینهسازی JavaScript و CSS
روش | بهترین برای | پیچیدگی پیادهسازی | ریسک | تأثیر بر Core Web Vitals |
---|---|---|---|---|
Critical CSS (LCP) | سایتهای با طراحی ثابت | زیاد | متوسط | بسیار زیاد |
JS به تعویق انداختن (FID) | تمام سایتها | کم | زیاد | زیاد |
ترکیب فایلها | سایتهای کوچک تا متوسط | متوسط | متوسط | زیاد |
بهینهسازی کلی | تمام سایتها | کم تا متوسط | کم | متوسط |
Code Splitting | سایتهای پیچیده | زیاد | متوسط | زیاد |
استفاده از افزونه | سایتهای متوسط | متوسط تا زیاد | کم | کم |
یکی از چالشهای اصلی در بهینهسازی CSS و JavaScript در وردپرس، تعادل بین عملکرد و عملکرد است. برخی از روشهای بهینهسازی ممکن است با برخی افزونهها یا قالبها ناسازگار باشند. بنابراین، همیشه پس از هر تغییر، سایت را به دقت تست کنید و از یک محیط آزمایشی استفاده کنید.
به عنوان یک استراتژی کلی، بهتر است ابتدا با روشهای کم خطر مانند به تعویق انداختن JavaScript و مینیفای کردن فایلها شروع کنید، سپس به سراغ روشهای پیشرفتهتر مانند Critical CSS و Code Splitting بروید. همچنین، استفاده از یک افزونه جامع مانند WP Rocket میتواند بسیاری از این بهینهسازیها را به صورت خودکار انجام دهد.
در بخش بعدی، به بررسی یکی از موثرترین راههای بهبود عملکرد وردپرس، یعنی استفاده از افزونههای کش وردپرس خواهیم پرداخت.
7. استفاده بهینه از سیستمهای کش در وردپرس برای بهبود سرعت
افزونههای کش وردپرس یکی از موثرترین راههای بهبود Core Web Vitals هستند. کش کردن به معنای ذخیره نسخههای ایستا از محتوای پویای سایت است، که میتواند زمان پردازش و بارگذاری را به طور چشمگیری کاهش دهد. سایتهای وردپرسی به طور پیشفرض در هر درخواست، کوئریهای متعددی به دیتابیس ارسال میکنند و فایلهای PHP را اجرا میکنند، که میتواند زمانبر باشد. با استفاده از سیستمهای کش، میتوان این فرآیند را بهینه کرد و TTFB (Time To First Byte) را تا 90% کاهش داد.
لایههای مختلف کش در وردپرس
یک استراتژی جامع کش در وردپرس باید چندین لایه داشته باشد:
- کش مرورگر: اولین و سریعترین لایه کش
- کش CDN: ذخیره محتوا در سرورهای نزدیک به کاربر
- کش صفحه: ذخیره خروجی HTML کامل صفحات
- کش اشیاء: ذخیره نتایج کوئریهای دیتابیس و API
- کش فرگمنت: ذخیره بخشهای خاصی از صفحه
- کش دیتابیس: ذخیره نتایج کوئریهای پرکاربرد
پیادهسازی کش مرورگر
کش مرورگر با استفاده از هدرهای HTTP مناسب کنترل میشود:
PHPfunction add_browser_caching_headers() {
if (!is_admin()) {
// هدرهای کش برای فایلهای ایستا
if (preg_match('/\.(jpg|jpeg|png|gif|ico|css|js|woff2|woff|ttf)$/', $_SERVER['REQUEST_URI'])) {
header('Cache-Control: public, max-age=31536000, immutable'); // 1 سال
header('Pragma: public');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
} else {
// هدرهای کش برای صفحات HTML
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
}
}
}
add_action('send_headers', 'add_browser_caching_headers');
این کد را میتوان در فایل functions.php اضافه کرد یا با افزودن کد زیر به فایل .htaccess (برای سرورهای Apache) پیادهسازی کرد:
Code<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType video/mp4 "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/pdf "access plus 1 month"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
</IfModule>
پیادهسازی کش صفحه
کش صفحه یکی از موثرترین روشهای کاهش TTFB است. میتوان آن را به صورت دستی با این کد پیادهسازی کرد:
PHPfunction page_cache_init() {
// مسیر فایل کش
$request_uri = md5($_SERVER['REQUEST_URI']);
$cache_file = WP_CONTENT_DIR . '/cache/page-cache/' . $request_uri . '.html';
// بررسی وجود فایل کش معتبر
if (file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ساعت
// بررسی استثناها
if (
!is_user_logged_in() &&
!isset($_POST['comment']) &&
!is_admin() &&
!is_404() &&
!is_search() &&
$_SERVER['REQUEST_METHOD'] !== 'POST'
) {
// اضافه کردن هدر کش
header('X-WP-Cache: HIT');
readfile($cache_file);
exit;
}
}
// شروع ذخیرهسازی خروجی
ob_start(function($buffer) use ($cache_file) {
// ذخیره فقط اگر پاسخ HTTP معتبر باشد
if (
!is_user_logged_in() &&
!isset($_POST['comment']) &&
!is_admin() &&
!is_404() &&
!is_search() &&
$_SERVER['REQUEST_METHOD'] !== 'POST' &&
http_response_code() == 200
) {
// اطمینان از وجود دایرکتوری کش
wp_mkdir_p(dirname($cache_file));
// افزودن زمان تولید کش
$buffer .= '<!-- Page Cached by WP at ' . date('Y-m-d H:i:s') . ' -->';
// ذخیره در فایل کش
file_put_contents($cache_file, $buffer, LOCK_EX);
}
return $buffer;
});
}
// اجرای قبل از بارگذاری وردپرس
add_action('plugins_loaded', 'page_cache_init', 1);
پیادهسازی کش اشیاء
کش اشیاء برای ذخیره نتایج کوئریهای دیتابیس و فراخوانیهای API استفاده میشود:
PHPfunction get_posts_with_cache($args = array()) {
// ساخت کلید کش بر اساس آرگومانها
$cache_key = 'posts_' . md5(serialize($args));
// بررسی وجود داده در کش
$posts = wp_cache_get($cache_key, 'posts');
if ($posts === false) {
// داده در کش وجود ندارد، باید از دیتابیس بخوانیم
$posts = get_posts($args);
// ذخیره در کش برای 1 ساعت
wp_cache_set($cache_key, $posts, 'posts', 3600);
}
return $posts;
}
// مثال استفاده
$recent_posts = get_posts_with_cache(array(
'post_type' => 'post',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC'
));
پیادهسازی کش فرگمنت
کش فرگمنت برای بخشهایی از صفحه مفید است که پویا هستند اما نیاز به بروزرسانی مداوم ندارند:
PHPfunction get_cached_fragment($fragment_name, $callback, $args = array(), $expiration = 3600) {
// ساخت کلید کش
$cache_key = 'fragment_' . $fragment_name . '_' . md5(serialize($args));
// بررسی وجود داده در کش
$output = wp_cache_get($cache_key, 'fragments');
if ($output === false) {
// اجرای تابع بازگرداننده محتوا
ob_start();
call_user_func_array($callback, $args);
$output = ob_get_clean();
// ذخیره در کش
wp_cache_set($cache_key, $output, 'fragments', $expiration);
}
return $output;
}
// مثال استفاده
function render_popular_posts() {
$posts = get_posts(array(
'meta_key' => 'views',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'posts_per_page' => 5
));
foreach ($posts as $post) {
echo '<li><a href="' . get_permalink($post->ID) . '">' . $post->post_title . '</a></li>';
}
}
// نمایش پستهای محبوب با کش
echo get_cached_fragment('popular_posts', 'render_popular_posts', array(), 3600);
افزونههای کش وردپرس
افزونههای کش وردپرس متعددی وجود دارند که میتوانند فرآیند کش را سادهتر کنند:
- WP Rocket: یک راهحل جامع کش با امکانات پیشرفته
- کش صفحه
- مینیفای و ترکیب CSS/JS
- تنبلبارگذاری تصاویر
- پیشبارگذاری هوشمند
- یکپارچگی با CDN
- W3 Total Cache: افزونه رایگان با امکانات گسترده
- کش صفحه، اشیاء، دیتابیس
- مینیفای CSS/JS
- یکپارچگی با CDN
- کش مرورگر
- LiteSpeed Cache: برای سرورهای LiteSpeed
- کش سمت سرور سریع
- بهینهسازی تصاویر
- تنبلبارگذاری
- مینیفای CSS/JS
- WP Super Cache: راهحل ساده و سبک
- تولید فایلهای HTML ایستا
- پیکربندی ساده
- سازگاری بالا
بهترین شیوههای استفاده از کش
- پاکسازی خودکار کش: پاکسازی خودکار کش پس از تغییرات محتوا:
PHPfunction clear_cache_on_post_update($post_id) {
// پاکسازی کش پست
wp_cache_delete('post_' . $post_id, 'posts');
// پاکسازی کش صفحه اصلی
$home_cache = WP_CONTENT_DIR . '/cache/page-cache/' . md5('/') . '.html';
if (file_exists($home_cache)) {
unlink($home_cache);
}
// پاکسازی کش آرشیو
$archive_cache = WP_CONTENT_DIR . '/cache/page-cache/' . md5('/category/' . get_post_type($post_id)) . '.html';
if (file_exists($archive_cache)) {
unlink($archive_cache);
}
}
add_action('save_post', 'clear_cache_on_post_update');
- استثناء کردن صفحات پویا: صفحاتی مانند سبد خرید یا فرمهای تماس نباید کش شوند:
PHPfunction should_bypass_cache() {
// صفحات پویا که نباید کش شوند
$bypass_urls = array(
'/cart/',
'/checkout/',
'/my-account/',
'/wp-admin/',
'/wp-login.php',
'/wp-cron.php'
);
foreach ($bypass_urls as $url) {
if (strpos($_SERVER['REQUEST_URI'], $url) !== false) {
return true;
}
}
// بررسی کوکیهای جلسه
if (!empty($_COOKIE['woocommerce_items_in_cart']) || !empty($_COOKIE['wordpress_logged_in'])) {
return true;
}
return false;
}
- کش هوشمند با شناسایی دستگاه: ارائه نسخههای مختلف کش برای موبایل و دسکتاپ:
PHPfunction get_device_specific_cache_key() {
$cache_key = md5($_SERVER['REQUEST_URI']);
// شناسایی دستگاه
if (wp_is_mobile()) {
$cache_key .= '_mobile';
} else {
$cache_key .= '_desktop';
}
return $cache_key;
}
- پیشگرم کردن کش: بازسازی خودکار کش پس از پاکسازی:
PHPfunction warm_up_cache() {
// لیست URLهای مهم
$urls = array(
home_url(),
home_url('/about/'),
home_url('/contact/'),
home_url('/blog/')
);
foreach ($urls as $url) {
// درخواست غیرهمزمان به URL
wp_remote_get($url, array(
'timeout' => 0.01,
'blocking' => false,
'sslverify' => false,
'user-agent' => 'WP Cache Warmer'
));
}
}
// پیشگرم کردن کش پس از پاکسازی
add_action('wp_cache_cleared', 'warm_up_cache');
افزونههای کش وردپرس یکی از سریعترین و موثرترین راهها برای بهبود Core Web Vitals هستند. با پیادهسازی درست سیستم کش، میتوانید TTFB را تا 90% کاهش دهید و LCP را به طور قابل توجهی بهبود بخشید. این بهبودها مستقیماً بر امتیاز 100 در Lighthouse تأثیر میگذارند.
در بخش بعدی، به بررسی راهکارهای بهبود FID خواهیم پرداخت که یکی دیگر از معیارهای مهم Core Web Vitals است و بر تعاملپذیری سایت تمرکز دارد.
8. راهحلهای بهبود FID و کاهش تأخیر در تعامل کاربر با سایت
FID (First Input Delay) چیست؟ این معیار زمان بین اولین تعامل کاربر با صفحه (مثلاً کلیک روی یک دکمه) و زمانی که مرورگر به آن واکنش نشان میدهد را اندازهگیری میکند. FID یکی از سه معیار اصلی Core Web Vitals است که مستقیماً به تجربه تعاملی کاربر مربوط میشود. بهبود FID برای بهینهسازی وردپرس ضروری است، زیرا سایتهای وردپرسی اغلب با چالشهای مرتبط با اجرای جاوااسکریپت مواجه هستند.
درک عوامل موثر بر FID
قبل از بهینهسازی، باید درک درستی از عوامل تأثیرگذار بر FID داشته باشیم:
- اجرای جاوااسکریپت طولانی: مهمترین عامل FID ضعیف، اجرای کدهای جاوااسکریپت طولانی در thread اصلی مرورگر است.
- بارگذاری جاوااسکریپتهای سنگین: بارگذاری فایلهای بزرگ جاوااسکریپت، مخصوصاً در شروع بارگذاری صفحه.
- رویدادهای متعدد: ثبت تعداد زیادی رویداد که باید در thread اصلی پردازش شوند.
- تداخل افزونهها: در وردپرس، افزونههای متعدد میتوانند کدهای جاوااسکریپت متعددی را اضافه کنند که با هم تداخل دارند.
تکنیکهای بهبود FID در وردپرس
1. تقسیم کارهای جاوااسکریپت
یکی از موثرترین روشها برای بهبود FID، تقسیم کارهای طولانی جاوااسکریپت به قطعات کوچکتر است:
JAVASCRIPT// قبل از بهینهسازی: یک تابع طولانی که thread اصلی را مسدود میکند
function processLargeData(data) {
for (let i = 0; i < data.length; i++) {
// پردازش دادهها
}
}
// بعد از بهینهسازی: تقسیم به قطعات کوچکتر
function processLargeDataOptimized(data) {
const chunkSize = 100;
let index = 0;
function processChunk() {
const chunk = data.slice(index, index + chunkSize);
index += chunkSize;
// پردازش قطعه فعلی
for (let i = 0; i < chunk.length; i++) {
// پرداز دادهها
}
// اگر هنوز داده برای پردازش وجود دارد، قطعه بعدی را زمانبندی کن
if (index < data.length) {
setTimeout(processChunk, 0);
}
}
// شروع پردازش
processChunk();
}
این تکنیک به مرورگر اجازه میدهد بین قطعات کد، به رویدادهای کاربر پاسخ دهد.
2. استفاده از Web Workers
Web Workers امکان اجرای کدهای سنگین در threadهای جداگانه را فراهم میکنند:
JAVASCRIPT// main.js - اسکریپت اصلی
document.addEventListener('DOMContentLoaded', function() {
// ایجاد یک Web Worker
const worker = new Worker('/wp-content/themes/your-theme/assets/js/worker.js');
// ارسال داده به worker
worker.postMessage({
action: 'processData',
data: largeDataSet
});
// دریافت نتیجه از worker
worker.onmessage = function(e) {
const result = e.data;
// استفاده از نتیجه بدون مسدود کردن thread اصلی
updateUI(result);
};
});
// worker.js - کد Web Worker
self.addEventListener('message', function(e) {
const { action, data } = e.data;
if (action === 'processData') {
// پردازش دادههای سنگین
const result = processLargeData(data);
// ارسال نتیجه به اسکریپت اصلی
self.postMessage(result);
}
});
function processLargeData(data) {
// پردازش سنگین اینجا انجام میشود
// ...
return processedData;
}
برای پیادهسازی Web Workers در وردپرس:
PHPfunction enqueue_web_worker() {
wp_enqueue_script('main-script', get_template_directory_uri() . '/assets/js/main.js', array(), '1.0.0', true);
// ثبت Service Worker Path
echo '<script>
const WORKER_PATH = "' . get_template_directory_uri() . '/assets/js/worker.js";
</script>';
}
add_action('wp_enqueue_scripts', 'enqueue_web_worker');
3. به تعویق انداختن جاوااسکریپت غیرضروری
کدهای جاوااسکریپتی که برای تعامل اولیه کاربر ضروری نیستند را به تعویق بیندازید:
PHPfunction defer_non_critical_js() {
// لیست اسکریپتهای غیرضروری
$scripts_to_defer = array(
'comment-reply',
'analytics',
'social-sharing',
'chat-widget'
);
global $wp_scripts;
foreach ($scripts_to_defer as $handle) {
if (isset($wp_scripts->registered[$handle])) {
// تغییر اولویت به پایین
$wp_scripts->registered[$handle]->deps[] = 'jquery';
// افزودن defer
add_filter('script_loader_tag', function($tag, $script_handle) use ($handle) {
if ($script_handle === $handle) {
return str_replace(' src', ' defer src', $tag);
}
return $tag;
}, 10, 2);
}
}
}
add_action('wp_enqueue_scripts', 'defer_non_critical_js', 99);
4. استفاده از requestIdleCallback
این API به شما امکان میدهد کدها را فقط زمانی که مرورگر بیکار است اجرا کنید:
JAVASCRIPT// اضافه کردن به فایل جاوااسکریپت تم
function scheduleNonCriticalTasks() {
const tasks = [
initSocialShareButtons,
loadRelatedPosts,
initCommentSystem,
trackUserBehavior
];
if ('requestIdleCallback' in window) {
tasks.forEach(task => {
requestIdleCallback(() => {
task();
}, { timeout: 2000 });
});
} else {
// پشتیبانی از مرورگرهای قدیمی
setTimeout(() => {
tasks.forEach(task => task());
}, 200);
}
}
// اجرا پس از بارگذاری صفحه
window.addEventListener('load', scheduleNonCriticalTasks);
5. بهینهسازی افزونههای وردپرس
افزونههای وردپرس یکی از منابع اصلی کدهای جاوااسکریپت سنگین هستند:
- شناسایی افزونههای مشکلساز:
PHPfunction analyze_plugin_scripts() {
if (current_user_can('administrator') && isset($_GET['analyze_scripts'])) {
global $wp_scripts;
echo '<div style="background: #fff; padding: 20px; margin: 20px; border: 1px solid #ccc;">';
echo '<h2>Script Analysis</h2>';
echo '<table style="width: 100%; border-collapse: collapse;">';
echo '<tr><th style="text-align: left; border: 1px solid #ccc; padding: 5px;">Handle</th><th style="text-align: left; border: 1px solid #ccc; padding: 5px;">Source</th><th style="text-align: left; border: 1px solid #ccc; padding: 5px;">Size</th></tr>';
foreach ($wp_scripts->queue as $handle) {
$script = $wp_scripts->registered[$handle];
$path = ABSPATH . str_replace(site_url('/'), '', $script->src);
$size = file_exists($path) ? round(filesize($path) / 1024, 2) . ' KB' : 'N/A';
// تشخیص افزونه از URL
$plugin_path = preg_match('/wp-content\/plugins\/([^\/]+)\//', $script->src, $matches);
$plugin_name = $plugin_path ? $matches[1] : 'Theme or Core';
echo '<tr>';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . $handle . '</td>';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . $plugin_name . '</td>';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . $size . '</td>';
echo '</tr>';
}
echo '</table>';
echo '</div>';
}
}
add_action('wp_footer', 'analyze_plugin_scripts', 999);
برای استفاده، ?analyze_scripts=1 را به URL سایت خود اضافه کنید.
- غیرفعال کردن هوشمند افزونهها:
PHPfunction conditionally_deactivate_plugins() {
// غیرفعال کردن افزونهها در صفحات خاص
if (is_page('landing-page') || is_page('sales-page')) {
// لیست افزونههایی که میخواهید غیرفعال کنید
$plugins_to_deactivate = array(
'contact-form-7/wp-contact-form-7.php',
'woocommerce/woocommerce.php',
'jetpack/jetpack.php'
);
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
// ذخیره وضعیت فعلی
$active_plugins = get_option('active_plugins');
$new_active_plugins = array_diff($active_plugins, $plugins_to_deactivate);
// بروزرسانی موقت افزونههای فعال
update_option('active_plugins', $new_active_plugins);
// بازگرداندن وضعیت اصلی در پایان اجرا
add_action('shutdown', function() use ($active_plugins) {
update_option('active_plugins', $active_plugins);
});
}
}
add_action('plugins_loaded', 'conditionally_deactivate_plugins', 1);
- کنترل دقیق اسکریپتهای افزونهها:
PHPfunction control_plugin_scripts() {
// غیرفعال کردن اسکریپتهای خاص
wp_dequeue_script('contact-form-7');
wp_dequeue_script('wc-cart-fragments');
// بارگذاری شرطی اسکریپتها
if (!is_page('contact')) {
wp_dequeue_script('contact-form-7');
}
if (!is_woocommerce() && !is_cart() && !is_checkout()) {
wp_dequeue_script('woocommerce');
wp_dequeue_script('wc-cart-fragments');
}
}
add_action('wp_enqueue_scripts', 'control_plugin_scripts', 100);
6. بهینهسازی رویدادهای DOM
رویدادهای متعدد DOM میتوانند FID را تحت تأثیر قرار دهند:
JAVASCRIPT// قبل از بهینهسازی: رویدادهای متعدد scroll
window.addEventListener('scroll', updateHeader);
window.addEventListener('scroll', lazyLoadImages);
window.addEventListener('scroll', showScrollToTop);
window.addEventListener('scroll', trackScrollDepth);
// بعد از بهینهسازی: یک رویداد throttle شده
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// یک event listener برای همه عملیات scroll
window.addEventListener('scroll', throttle(function() {
updateHeader();
lazyLoadImages();
showScrollToTop();
trackScrollDepth();
}, 100));
7. استفاده از IntersectionObserver برای اسکرول
به جای رویدادهای scroll سنتی، از IntersectionObserver استفاده کنید:
JAVASCRIPTdocument.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('.animate-on-scroll');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // فقط یک بار اجرا شود
}
});
}, {
threshold: 0.1 // هنگامی که 10% از عنصر قابل مشاهده است
});
elements.forEach(element => {
observer.observe(element);
});
});
8. پیادهسازی بارگذاری تنبل برای کامپوننتهای سنگین
برای کامپوننتهای پیچیده مانند نقشهها، ویدیوها یا ویجتهای شخص ثالث، از بارگذاری تنبل استفاده کنید:
JAVASCRIPTdocument.addEventListener('DOMContentLoaded', function() {
const heavyComponents = document.querySelectorAll('.lazy-component');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const component = entry.target;
const type = component.dataset.type;
switch (type) {
case 'map':
loadGoogleMap(component);
break;
case 'video':
loadYouTubeVideo(component);
break;
case 'comments':
loadDisqusComments(component);
break;
}
observer.unobserve(component);
}
});
}, {
rootMargin: '200px' // بارگذاری کمی قبل از رسیدن به مؤلفه
});
heavyComponents.forEach(component => {
observer.observe(component);
});
});
function loadGoogleMap(container) {
const script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap';
script.defer = true;
document.body.appendChild(script);
}
function loadYouTubeVideo(container) {
const videoId = container.dataset.videoId;
container.innerHTML = `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe>`;
}
function loadDisqusComments(container) {
const script = document.createElement('script');
script.src = 'https://your-site.disqus.com/embed.js';
script.setAttribute('data-timestamp', +new Date());
document.body.appendChild(script);
}
تست و مانیتورینگ FID
برای اطمینان از بهبود FID، باید آن را به طور مداوم تست و مانیتور کنید:
- استفاده از Total Blocking Time در Lighthouse: FID در آزمایشگاه قابل اندازهگیری نیست، اما Total Blocking Time (TBT) با آن همبستگی بالایی دارد.
- ثبت FID واقعی با web-vitals.js:
JAVASCRIPTfunction sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
url: location.href
});
// ارسال به سرویس آنالیتیکس
navigator.sendBeacon('/analytics', body);
}
// اضافه کردن به هدر سایت
wp_add_inline_script('jquery', "
import {getFID} from 'https://unpkg.com/[email protected]/dist/web-vit als.attribution.js';
getFID(sendToAnalytics);
");
جدول ۷: تأثیر تکنیکهای مختلف بر بهبود FID
تکنیک | بهترین برای | پیچیدگی پیادهسازی | تأثیر بر FID |
---|---|---|---|
تقسیم کارهای جاوااسکریپت | سایتهای با پردازش داده زیاد | متوسط | بسیار زیاد |
Web Workers | عملیات سنگین مانند فیلتر محصولات | بسیار زیاد | زیاد |
به تعویق انداختن جاوااسکریپت | تمام سایتها | کم | زیاد |
requestIdleCallback | تمام سایتها | متوسط | کم |
بهینهسازی افزونهها | سایتهای با افزونههای متعدد | زیاد | متوسط |
بهینهسازی رویدادها | سایتهای تعاملی | کم | متوسط |
IntersectionObserver | سایتهای با اسکرول زیاد | متوسط | متوسط |
بارگذاری تنبل کامپوننتها | سایتهای با ویجتهای شخص ثالث | زیاد | متوسط |
بهبود FID (First Input Delay) چیست یکی از چالشبرانگیزترین جنبههای Core Web Vitals است، زیرا به رفتار جاوااسکریپت سایت بستگی دارد. اما با پیادهسازی تکنیکهای فوق، میتوانید FID را به طور قابل توجهی بهبود بخشید و به امتیاز 100 در Lighthouse نزدیکتر شوید.
در بخش بعدی، به بررسی راهکارهای کاهش CLS خواهیم پرداخت که مربوط به ثبات بصری صفحه است و تأثیر قابل توجهی بر تجربه کاربری دارد.
9. کاهش CLS و جلوگیری از جابجایی المانهای صفحه در وردپرس
CLS (Cumulative Layout Shift) چیست؟ این معیار میزان جابجایی ناخواسته عناصر صفحه در طول بارگذاری را اندازهگیری میکند. CLS زمانی اتفاق میافتد که عناصری مانند تصاویر، تبلیغات، فونتها یا محتوای پویا بدون تخصیص فضای کافی بارگذاری میشوند و باعث “پرش” عناصر دیگر میشوند. کاهش CLS یکی از مهمترین جنبههای بهینهسازی وردپرس برای دستیابی به امتیاز 100 در Lighthouse است.
دلایل اصلی CLS بالا در سایتهای وردپرسی
قبل از بررسی راهحلها، باید دلایل اصلی CLS بالا در وردپرس را بشناسیم:
- تصاویر بدون ابعاد مشخص: تصاویری که width و height آنها تعیین نشده است.
- فونتهای وب: تغییر فونتها پس از بارگذاری میتواند باعث تغییر اندازه متن شود.
- تبلیغات و ویجتهای شخص ثالث: عناصری که بعداً به DOM اضافه میشوند.
- محتوای تزریق شده با جاوااسکریپت: مانند نظرات، پاپآپها و اعلانها.
- انیمیشنها: انیمیشنهای نامناسب که باعث تغییر لایهبندی میشوند.
تکنیکهای کاهش CLS در وردپرس
1. تعیین ابعاد دقیق تصاویر
تعیین ابعاد تصاویر یکی از موثرترین راههای کاهش CLS است:
PHPfunction add_image_dimensions_to_content($content) {
return preg_replace_callback('/<img(.*?)>/', function($matches) {
$img_tag = $matches[0];
// اگر width و height وجود دارند، تغییری ایجاد نکن
if (strpos($img_tag, 'width=') !== false && strpos($img_tag, 'height=') !== false) {
return $img_tag;
}
// استخراج URL تصویر
preg_match('/src="([^"]*)"/', $img_tag, $src_match);
if (empty($src_match[1])) return $img_tag;
$src = $src_match[1];
// تبدیل URL به مسیر فایل
$upload_dir = wp_upload_dir();
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $src);
// بررسی وجود فایل
if (file_exists($file_path)) {
// دریافت ابعاد تصویر
list($width, $height) = getimagesize($file_path);
// افزودن ابعاد به تگ img
$img_tag = str_replace('<img', '<img width="' . $width . '" height="' . $height . '"', $img_tag);
// افزودن style برای حفظ نسبت ابعاد در صورت استفاده از max-width
if (strpos($img_tag, 'style=') === false) {
$img_tag = str_replace('<img', '<img style="aspect-ratio: ' . $width . '/' . $height . ';"', $img_tag);
}
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'add_image_dimensions_to_content', 99);
همچنین میتوانید از CSS برای اطمینان از حفظ فضای تصاویر استفاده کنید:
CSS/* اضافه کردن به فایل style.css تم */
img {
height: auto;
max-width: 100%;
aspect-ratio: attr(width) / attr(height);
}
/* برای تصاویر با نسبت ابعاد ثابت مانند تصاویر محصول */
.product-image {
position: relative;
width: 100%;
padding-bottom: 100%; /* نسبت 1:1 */
overflow: hidden;
}
.product-image img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
2. مدیریت بارگذاری فونتها
فونتهای وب میتوانند باعث “Flash of Unstyled Text” (FOUT) یا “Flash of Invisible Text” (FOIT) شوند که به CLS بالا منجر میشود:
PHPfunction optimize_font_loading() {
?>
<script>
// استفاده از Font Loading API
if ('fonts' in document) {
// پیشبارگذاری فونتها
const fontFamilies = [
'Your Primary Font:400',
'Your Primary Font:700',
'Your Secondary Font:400'
];
Promise.all(
fontFamilies.map(font => document.fonts.load(font))
).then(() => {
document.documentElement.classList.add('fonts-loaded');
});
}
</script>
<style>
/* استفاده از font-display: swap برای جلوگیری از FOIT */
@font-face {
font-family: 'Your Primary Font';
src: url('<?php echo get_template_directory_uri(); ?>/assets/fonts/primary.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* استفاده از system-ui به عنوان fallback */
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Your Primary Font', sans-serif;
}
/* اعمال فونتهای سفارشی فقط پس از بارگذاری */
.fonts-loaded body {
font-family: 'Your Primary Font', sans-serif;
}
/* اطمینان از حفظ فضا با استفاده از size-adjust */
@font-face {
font-family: 'Your Primary Font Fallback';
size-adjust: 97%;
src: local('Arial');
}
</style>
<?php
}
add_action('wp_head', 'optimize_font_loading', 1);
برای بهینهسازی فونت در وردپرس، از تکنیکهای زیر استفاده کنید:
- پیشبارگذاری فونتهای ضروری:
PHPfunction preload_critical_fonts() {
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/fonts/primary.woff2" as="font" type="font/woff2" crossorigin>';
}
add_action('wp_head', 'preload_critical_fonts', 1);
- استفاده از فونتهای سیستمی برای کاهش نیاز به دانلود:
CSS/* استفاده از فونتهای سیستمی مدرن */
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
}
- محدود کردن تعداد وزنهای فونت:
PHPfunction limit_font_weights() {
wp_dequeue_style('google-fonts');
wp_enqueue_style('limited-google-fonts', 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap', array(), null);
}
add_action('wp_enqueue_scripts', 'limit_font_weights', 999);
3. مدیریت تبلیغات و ویجتهای شخص ثالث
تبلیغات و ویجتهای شخص ثالث از منابع اصلی CLS هستند:
PHPfunction reserve_space_for_ads() {
?>
<style>
/* رزرو فضا برای تبلیغات */
.ad-container {
min-height: 250px;
width: 100%;
background-color: #f9f9f9;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
position: relative;
}
.ad-container::before {
content: "Advertisement";
color: #999;
font-size: 12px;
position: absolute;
top: 5px;
left: 0;
right: 0;
text-align: center;
}
</style>
<script>
// بارگذاری تبلیغات فقط پس از بارگذاری کامل صفحه
window.addEventListener('load', function() {
const adContainers = document.querySelectorAll('.ad-container');
adContainers.forEach(container => {
// کد بارگذاری تبلیغات
const adCode = container.dataset.adCode;
if (adCode) {
container.innerHTML = atob(adCode); // استفاده از Base64 برای کد تبلیغات
}
});
});
</script>
<?php
}
add_action('wp_head', 'reserve_space_for_ads');
برای ویجتهای شخص ثالث مانند نقشهها، ویدیوها و اسکریپتهای چت:
HTML<!-- استفاده در قالب -->
<div class="widget-container" style="min-height: 400px; position: relative;">
<div class="widget-placeholder" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background-color: #f5f5f5;">
<div>Loading Map...</div>
</div>
<div class="widget-content" data-widget-type="map" data-lat="35.6895" data-lng="139.6917" style="opacity: 0; transition: opacity 0.3s;"></div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const widgets = document.querySelectorAll('.widget-content');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const widget = entry.target;
const type = widget.dataset.widgetType;
setTimeout(() => {
loadWidget(widget, type);
widget.style.opacity = 1;
// حذف placeholder پس از بارگذاری
const placeholder = widget.previousElementSibling;
if (placeholder && placeholder.classList.contains('widget-placeholder')) {
placeholder.style.opacity = 0;
setTimeout(() => placeholder.remove(), 300);
}
}, 100);
observer.unobserve(widget);
}
});
}, {
rootMargin: '200px'
});
widgets.forEach(widget => {
observer.observe(widget);
});
});
function loadWidget(element, type) {
switch (type) {
case 'map':
loadGoogleMap(element, element.dataset.lat, element.dataset.lng);
break;
case 'video':
loadYouTubeVideo(element, element.dataset.videoId);
break;
case 'chat':
loadChatWidget(element);
break;
}
}
</script>
4. مدیریت محتوای تزریق شده با جاوااسکریپت
برای محتوایی که با جاوااسکریپت تزریق میشود، باید فضای کافی از قبل رزرو کنید:
PHPfunction reserve_space_for_dynamic_content() {
?>
<style>
/* رزرو فضا برای نظرات */
.comments-container {
min-height: 200px;
}
/* رزرو فضا برای محصولات مرتبط */
.related-products {
min-height: 300px;
}
/* رزرو فضا برای نوتیفیکیشنها */
.notification-area {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
min-height: 0;
z-index: 1000;
}
</style>
<?php
}
add_action('wp_head', 'reserve_space_for_dynamic_content');
برای اعلانها و پاپآپها، از انیمیشنهایی استفاده کنید که CLS ایجاد نکنند:
CSS/* استفاده از انیمیشنهای مناسب برای پاپآپها */
.popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9);
opacity: 0;
z-index: 1000;
transition: opacity 0.3s, transform 0.3s;
pointer-events: none;
}
.popup.active {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
pointer-events: auto;
}
/* استفاده از انیمیشنهای مناسب برای اعلانها */
.notification {
transform: translateX(120%);
opacity: 0;
transition: transform 0.3s, opacity 0.3s;
}
.notification.show {
transform: translateX(0);
opacity: 1;
}
5. بهینهسازی انیمیشنها
انیمیشنها میتوانند منبع CLS باشند. برای بهینهسازی آنها:
CSS/* استفاده از transform به جای تغییر width/height */
.grow-animation {
/* بد: تغییر ابعاد */
/*
width: 100px;
height: 100px;
transition: width 0.3s, height 0.3s;
*/
/* خوب: استفاده از transform */
width: 100px;
height: 100px;
transform: scale(1);
transition: transform 0.3s;
}
.grow-animation:hover {
/* بد: تغییر ابعاد */
/*
width: 120px;
height: 120px;
*/
/* خوب: استفاده از transform */
transform: scale(1.2);
}
/* استفاده از opacity و transform برای انیمیشنها */
@keyframes slideIn {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.animated-element {
animation: slideIn 0.3s ease forwards;
}
6. اندازهگیری و دیباگ CLS
برای شناسایی دقیق منابع CLS، میتوانید از ابزارهای دیباگ استفاده کنید:
JAVASCRIPT// اضافه کردن به فایل جاوااسکریپت تم
function debugLayoutShifts() {
if (!window.location.search.includes('debug_cls=1')) return;
let cumulativeScore = 0;
let shifts = [];
// مشاهده تغییرات لایهبندی
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// محاسبه امتیاز
const score = entry.value;
cumulativeScore += score;
// ثبت اطلاعات تغییر لایهبندی
shifts.push({
timestamp: entry.startTime,
score: score.toFixed(4),
elements: entry.sources.map(source => {
const node = source.node;
return {
nodeName: node ? node.nodeName : 'Unknown',
className: node ? node.className : '',
id: node ? node.id : '',
innerText: node ? (node.innerText || '').substring(0, 20) : ''
};
})
});
// نمایش اطلاعات در کنسول
console.log('Layout Shift:', {
score: score.toFixed(4),
cumulative: cumulativeScore.toFixed(4),
elements: entry.sources.map(source => source.node)
});
// هایلایت کردن عناصر مشکلساز
entry.sources.forEach(source => {
if (source.node) {
const originalBackground = source.node.style.backgroundColor;
const originalOutline = source.node.style.outline;
source.node.style.backgroundColor = 'rgba(255, 0, 0, 0.2)';
source.node.style.outline = '2px solid red';
setTimeout(() => {
source.node.style.backgroundColor = originalBackground;
source.node.style.outline = originalOutline;
}, 2000);
}
});
}
});
observer.observe({ type: 'layout-shift', buffered: true });
// نمایش اطلاعات CLS در صفحه
const debugPanel = document.createElement('div');
debugPanel.style.position = 'fixed';
debugPanel.style.bottom = '0';
debugPanel.style.right = '0';
debugPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
debugPanel.style.color = 'white';
debugPanel.style.padding = '10px';
debugPanel.style.zIndex = '9999';
debugPanel.style.maxHeight = '50vh';
debugPanel.style.overflowY = 'auto';
debugPanel.style.maxWidth = '400px';
debugPanel.innerHTML = '<h3>CLS Debug</h3><div id="cls-score">Cumulative Score: 0</div><div id="cls-shifts"></div>';
document.body.appendChild(debugPanel);
// بروزرسانی پنل دیباگ هر 500ms
setInterval(() => {
document.getElementById('cls-score').textContent = `Cumulative Score: ${cumulativeScore.toFixed(4)}`;
const shiftsContainer = document.getElementById('cls-shifts');
shiftsContainer.innerHTML = '';
shifts.slice(-5).forEach(shift => {
const shiftEl = document.createElement('div');
shiftEl.style.marginBottom = '10px';
shiftEl.style.borderBottom = '1px solid rgba(255, 255, 255, 0.2)';
shiftEl.style.paddingBottom = '5px';
shiftEl.innerHTML = `
<div>Score: ${shift.score}</div>
<div>Time: ${Math.round(shift.timestamp)}ms</div>
<div>Elements: ${shift.elements.map(el =>
`<span style="color: yellow;">${el.nodeName}${el.className ? '.' + el.className.replace(/\s+/g, '.') : ''}${el.id ? '#' + el.id : ''}</span>`
).join(', ')}</div>
`;
shiftsContainer.appendChild(shiftEl);
});
}, 500);
}
// اجرای تابع دیباگ
document.addEventListener('DOMContentLoaded', debugLayoutShifts);
برای استفاده از این ابزار دیباگ، ?debug_cls=1 را به URL سایت خود اضافه کنید.
جدول ۸: تأثیر تکنیکهای مختلف بر کاهش CLS
تکنیک | بهترین برای | پیچیدگی پیادهسازی | تأثیر بر CLS |
---|---|---|---|
تعیین ابعاد تصاویر | تمام سایتها | کم | بسیار زیاد |
تعیین ابعاد فونت | سایتهای با فونتهای سفارشی | زیاد | متوسط |
رزرو فضا برای تبلیغات | سایتهای با تبلیغات | کم | بسیار زیاد |
مدیریت محتوای پویا | سایتهای با محتوای تزریق شده | زیاد | متوسط |
بهینهسازی انیمیشنها | سایتهای با انیمیشن زیاد | متوسط | متوسط |
استفاده از aspect-ratio | تمام سایتها | کم | زیاد |
بهینهسازی فونت | سایتهای با فونتهای سفارشی | متوسط | کم |
کمک به شناسایی CLS در محیط توسعه | توسعهدهندگان | کم | رایگان |
کاهش CLS (Cumulative Layout Shift) چیست یک جنبه حیاتی از بهینهسازی وردپرس برای دستیابی به امتیاز 100 در Lighthouse است. با پیادهسازی این تکنیکها، میتوانید ثبات بصری سایت خود را بهبود بخشید و تجربه کاربری بهتری ارائه دهید.
در بخش بعدی، به بررسی نقش قالبهای وردپرس در Core Web Vitals و نحوه انتخاب و بهینهسازی قالب مناسب خواهیم پرداخت.
10. انتخاب و بهینهسازی قالب وردپرس برای Core Web Vitals
قالب وردپرس یکی از تأثیرگذارترین عوامل بر Core Web Vitals است. یک قالب سنگین با کدهای غیربهینه میتواند تمام تلاشهای شما برای بهینهسازی وردپرس را خنثی کند. در مقابل، قالبهای سریع وردپرس میتوانند به شما در دستیابی به امتیاز 100 در Lighthouse کمک شایانی کنند. در این بخش، معیارهای انتخاب قالب مناسب و تکنیکهای بهینهسازی قالب فعلی را بررسی میکنیم.
معیارهای انتخاب قالب سریع و بهینه
1. کد پایه سبک و بهینه
قالبهای مناسب باید دارای کد پایه سبک و بهینه باشند:
- حجم کم فایلهای CSS و JS: قالبهای خوب معمولاً کمتر از 100KB CSS و 200KB JS دارند.
- استفاده از تکنیکهای مدرن کدنویسی: Flexbox و Grid به جای فریمورکهای سنگین.
- استفاده حداقلی از افزونههای خارجی: قالبهایی که به افزونههای متعدد وابسته نیستند.
برای ارزیابی کد پایه یک قالب، میتوانید از این روش استفاده کنید:
PHPfunction analyze_theme_performance() {
if (!current_user_can('administrator') || !isset($_GET['analyze_theme'])) return;
$theme_dir = get_template_directory();
$css_size = 0;
$js_size = 0;
$php_files = 0;
$total_lines = 0;
// بررسی فایلهای CSS
foreach (glob($theme_dir . '/**/*.css', GLOB_NOSORT) as $css_file) {
$css_size += filesize($css_file);
}
// بررسی فایلهای JS
foreach (glob($theme_dir . '/**/*.js', GLOB_NOSORT) as $js_file) {
$js_size += filesize($js_file);
}
// بررسی فایلهای PHP
foreach (glob($theme_dir . '/**/*.php', GLOB_NOSORT) as $php_file) {
$php_files++;
$total_lines += count(file($php_file));
}
echo '<div style="background: #fff; padding: 20px; margin: 20px; border: 1px solid #ccc;">';
echo '<h2>Theme Analysis: ' . wp_get_theme()->get('Name') . '</h2>';
echo '<p>Total CSS Size: ' . round($css_size / 1024, 2) . ' KB</p>';
echo '<p>Total JS Size: ' . round($js_size / 1024, 2) . ' KB</p>';
echo '<p>PHP Files: ' . $php_files . '</p>';
echo '<p>Total PHP Lines: ' . $total_lines . '</p>';
// بررسی وابستگیهای قالب
$theme = wp_get_theme();
$requires_plugins = $theme->get('RequiresPlugins');
if ($requires_plugins) {
echo '<p>Required Plugins: ' . $requires_plugins . '</p>';
}
echo '</div>';
}
add_action('wp_footer', 'analyze_theme_performance');
2. پشتیبانی از تکنیکهای مدرن بهینهسازی
قالبهای مناسب باید از تکنیکهای مدرن بهینهسازی پشتیبانی کنند:
- Lazy Loading بومی: پشتیبانی از ویژگی loading=“lazy” برای تصاویر.
- تصاویر واکنشگرا: استفاده از srcset و sizes برای ارائه تصاویر با اندازه مناسب.
- استفاده از فرمتهای مدرن تصویر: پشتیبانی از WebP و AVIF.
- بارگذاری تنبل اسکریپتها: استفاده از defer و async برای اسکریپتها.
3. طراحی واکنشگرا بدون CLS
قالبهای خوب باید طراحی واکنشگرای بدون CLS داشته باشند:
- استفاده از CSS Grid و Flexbox: به جای فریمورکهای سنگین مانند Bootstrap.
- رزرو فضا برای عناصر متغیر: مانند تصاویر، ویدیوها و تبلیغات.
- انیمیشنهای بهینه: استفاده از transform و opacity به جای تغییر ابعاد.
4. بررسی امتیاز Core Web Vitals قالبها
قبل از خرید قالب، امتیاز Core Web Vitals نسخه دمو آن را بررسی کنید:
- URL دموی قالب را در PageSpeed Insights وارد کنید.
- نتایج را برای نسخههای موبایل و دسکتاپ بررسی کنید.
- به دنبال قالبهایی باشید که حداقل امتیاز 80+ در موبایل دارند.
نوع قالب | تأثیر بر Core Web Vitals | مزایا | معایب |
---|---|---|---|
قالبهای سبک تخصصی | عالی | سرعت بالا، کد تمیز | امکانات محدودتر |
قالبهای چندمنظوره | متوسط تا ضعیف | امکانات متنوع، انعطافپذیری | کد سنگین، اسکریپتهای زیاد |
فریمورکهای قالب | متوسط | انعطافپذیری بالا | یادگیری پیچیده، نیاز به تنظیمات زیاد |
قالبهای AMP | عالی | سرعت بسیار بالا | محدودیتهای طراحی |
قالبهای سفارشی | بستگی به توسعهدهنده دارد | بهینه برای نیاز خاص | هزینه بالا، زمان توسعه طولانی |
5. قالبهای توصیه شده برای Core Web Vitals
برخی از بهترین قالبهای سریع وردپرس که برای Core Web Vitals بهینه شدهاند:
- GeneratePress: قالبی سبک با کد بهینه و امتیاز بالا در Lighthouse
- Astra: قالب سریع با امکانات سفارشیسازی متنوع
- Kadence: قالبی مدرن با تمرکز بر عملکرد
- Blocksy: قالب سازگار با ویرایشگر بلوکی با عملکرد عالی
- Neve: قالب سبک و سریع با تمرکز بر موبایل
بهینهسازی قالب فعلی وردپرس
اگر نمیخواهید قالب خود را تغییر دهید، میتوانید قالب فعلی را بهینه کنید:
1. حذف اسکریپتها و استایلهای غیرضروری
بسیاری از قالبها، فایلهای CSS و JS غیرضروری بارگذاری میکنند:
PHPfunction remove_unnecessary_theme_scripts() {
// حذف اسکریپتهای انیمیشن اگر از آنها استفاده نمیکنید
wp_dequeue_script('theme-animations');
// حذف اسلایدر اگر در صفحه جاری استفاده نمیشود
if (!has_shortcode(get_the_content(), 'theme-slider') && !is_front_page()) {
wp_dequeue_script('theme-slider');
wp_dequeue_style('theme-slider-style');
}
// حذف آیکونهای اضافی
if (!is_singular('portfolio')) {
wp_dequeue_style('theme-portfolio-icons');
}
// حذف فونتآیکونها اگر از آنها استفاده نمیکنید
if (!is_page_template('template-icons.php')) {
wp_dequeue_style('font-awesome');
}
}
add_action('wp_enqueue_scripts', 'remove_unnecessary_theme_scripts', 100);
2. بهینهسازی تصاویر پیشفرض قالب
بسیاری از قالبها، تصاویر بزرگ و غیربهینه دارند:
PHPfunction optimize_theme_images() {
// جایگزینی تصاویر هدر با نسخههای WebP
function replace_header_image_url($url) {
return str_replace('.jpg', '.webp', $url);
}
add_filter('theme_mod_header_image', 'replace_header_image_url');
// جایگزینی تصاویر پسزمینه با نسخههای بهینه
function replace_background_image($css) {
return str_replace(
'background-image: url("' . get_template_directory_uri() . '/assets/images/background.jpg")',
'background-image: url("' . get_template_directory_uri() . '/assets/images/background-optimized.webp")',
$css
);
}
add_filter('theme_mod_custom_css', 'replace_background_image');
}
add_action('after_setup_theme', 'optimize_theme_images');
3. بهینهسازی فایل header.php
فایل header.php معمولاً شامل کدهای زیادی است که میتوانند LCP را تحت تأثیر قرار دهند:
PHP// ایجاد یک فایل در مسیر child-theme/template-parts/optimized-header.php
function get_optimized_header() {
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php
// بارگذاری فقط CSS ضروری به صورت inline
$critical_css = file_get_contents(get_stylesheet_directory() . '/assets/css/critical.css');
echo '<style id="critical-css">' . $critical_css . '</style>';
// به تعویق انداختن بارگذاری CSS غیرضروری
echo '<link rel="preload" href="' . get_stylesheet_uri() . '" as="style" onload="this.onload=null;this.rel=\'stylesheet\'">';
echo '<noscript><link rel="stylesheet" href="' . get_stylesheet_uri() . '"></noscript>';
// پیشبارگذاری فونتهای ضروری
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin>';
// پیشبارگذاری تصویر هدر (احتمالاً LCP)
$header_image = get_header_image();
if ($header_image) {
echo '<link rel="preload" href="' . $header_image . '" as="image">';
}
wp_head();
?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<header id="masthead" class="site-header">
<!-- محتوای هدر بهینه شده -->
</header>
<?php
}
// استفاده از هدر بهینه شده
function use_optimized_header() {
remove_action('get_header', 'original_header_function');
add_action('get_header', 'get_optimized_header');
}
add_action('after_setup_theme', 'use_optimized_header');
4. بهینهسازی فوتر و ویجتها
فوتر و ویجتها میتوانند منبع بزرگی از کدهای غیربهینه باشند:
PHP// بهینهسازی فوتر
function optimize_footer_widgets() {
// به تعویق انداختن بارگذاری ویجتهای فوتر
if (!is_admin()) {
function delay_footer_widgets_scripts() {
?>
<script>
// بارگذاری تنبل ویجتهای فوتر
document.addEventListener('DOMContentLoaded', function() {
const footerWidgets = document.querySelector('.footer-widgets');
if (footerWidgets) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// بارگذاری اسکریپتهای ویجتهای فوتر
const scripts = [
'/wp-content/themes/your-theme/assets/js/instagram-widget.js',
'/wp-content/themes/your-theme/assets/js/twitter-widget.js'
];
scripts.forEach(src => {
const script = document.createElement('script');
script.src = src;
script.defer = true;
document.body.appendChild(script);
});
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '200px'
});
observer.observe(footerWidgets);
}
});
</script>
<?php
}
add_action('wp_footer', 'delay_footer_widgets_scripts');
}
}
add_action('widgets_init', 'optimize_footer_widgets');
5. بهینهسازی قالب برای موبایل
بهینهسازی قالب برای موبایل بسیار مهم است، زیرا Core Web Vitals در درجه اول بر اساس نسخه موبایل ارزیابی میشود:
PHPfunction optimize_theme_for_mobile() {
// ارائه تصاویر کوچکتر برای موبایل
function mobile_optimized_image_size($sizes) {
if (wp_is_mobile()) {
return '(max-width: 767px) 100vw, 50vw';
}
return $sizes;
}
add_filter('wp_calculate_image_sizes', 'mobile_optimized_image_size');
// حذف برخی عناصر در نسخه موبایل
if (wp_is_mobile()) {
function remove_heavy_elements_on_mobile($classes) {
$classes[] = 'mobile-optimized';
return $classes;
}
add_filter('body_class', 'remove_heavy_elements_on_mobile');
function add_mobile_optimization_css() {
?>
<style>
/* حذف عناصر غیرضروری در موبایل */
.mobile-optimized .desktop-only {
display: none !important;
}
/* سادهسازی منو در موبایل */
.mobile-optimized .main-navigation {
position: relative;
z-index: 100;
}
/* کاهش اندازه فونت در موبایل */
.mobile-optimized h1 {
font-size: 28px !important;
}
.mobile-optimized h2 {
font-size: 22px !important;
}
/* بهینهسازی فاصلهها در موبایل */
.mobile-optimized .entry-content {
padding: 15px !important;
}
</style>
<?php
}
add_action('wp_head', 'add_mobile_optimization_css');
}
}
add_action('wp', 'optimize_theme_for_mobile');
استفاده از Child Theme برای بهینهسازی
یکی از بهترین روشها برای بهینهسازی قالب، استفاده از Child Theme است:
PHP// functions.php در Child Theme
function child_theme_enqueue_styles() {
// بارگذاری استایلهای قالب والد به صورت بهینه
wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css', array(), wp_get_theme()->parent()->get('Version'));
// بارگ ذاری استایلهای Child Theme
wp_enqueue_style('child-style', get_stylesheet_uri(), array('parent-style'), wp_get_theme()->get('Version'));
// حذف اسکریپتهای غیرضروری قالب والد
wp_dequeue_script('parent-unnecessary-script');
// جایگزینی اسکریپتهای قالب والد با نسخههای بهینه
wp_dequeue_script('parent-slider');
wp_enqueue_script('optimized-slider', get_stylesheet_directory_uri() . '/assets/js/optimized-slider.js', array('jquery'), '1.0.0', true);
}
add_action('wp_enqueue_scripts', 'child_theme_enqueue_styles', 20);
// جایگزینی فایلهای قالب با نسخههای بهینه
function override_parent_template_parts() {
// جایگزینی header.php
add_filter('template_include', function($template) {
if (basename($template) === 'header.php') {
$new_template = get_stylesheet_directory() . '/optimized-header.php';
if (file_exists($new_template)) {
return $new_template;
}
}
return $template;
});
}
add_action('after_setup_theme', 'override_parent_template_parts');
انتخاب قالب مناسب و بهینهسازی آن یکی از مهمترین گامها در مسیر بهبود Core Web Vitals است. قالبهای سریع وردپرس میتوانند تا 50% در بهبود زمان بارگذاری و امتیاز 100 در Lighthouse تأثیر داشته باشند. با استفاده از تکنیکهای فوق، میتوانید قالب خود را برای عملکرد بهینه آماده کنید.
در بخش بعدی، به بررسی نقش افزونهها در Core Web Vitals و نحوه مدیریت بهینه آنها خواهیم پرداخت.
11. نقش افزونهها در Core Web Vitals و مدیریت بهینه آنها
افزونهها یکی از مهمترین عوامل تأثیرگذار بر Core Web Vitals در سایتهای وردپرسی هستند. هر افزونه میتواند فایلهای CSS، JavaScript، درخواستهای AJAX و کوئریهای دیتابیس اضافی به سایت شما اضافه کند. در این بخش، به بررسی تأثیر افزونهها بر عملکرد سایت، شناسایی افزونههای سنگین و معرفی افزونههای مفید برای بهبود Core Web Vitals میپردازیم.
تأثیر افزونهها بر Core Web Vitals
افزونهها میتوانند به چندین روش بر Core Web Vitals تأثیر بگذارند:
- افزایش TTFB: با اضافه کردن کوئریهای سنگین به دیتابیس
- تأخیر در LCP: با افزودن CSS و JavaScript مسدودکننده رندر
- افزایش FID: با اجرای اسکریپتهای سنگین در thread اصلی
- ایجاد CLS: با تزریق محتوا پس از بارگذاری اولیه صفحه
شناسایی افزونههای سنگین و مشکلساز
1. استفاده از Query Monitor
Query Monitor یکی از بهترین ابزارها برای شناسایی افزونههای سنگین است:
PHP// افزودن فیلترهای سفارشی به Query Monitor
function add_custom_qm_collectors($collectors) {
if (class_exists('QM_Collector')) {
class QM_Collector_Plugin_Impact extends QM_Collector {
public $id = 'plugin_impact';
public function name() {
return 'Plugin Impact';
}
public function process() {
$this->data['plugins'] = array();
// دریافت اطلاعات افزونههای فعال
$active_plugins = get_option('active_plugins');
foreach ($active_plugins as $plugin_path) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_path);
// محاسبه تعداد فایلهای CSS و JS
global $wp_styles, $wp_scripts;
$css_count = 0;
$js_count = 0;
$plugin_slug = dirname($plugin_path);
// بررسی استایلها
foreach ($wp_styles->registered as $handle => $style) {
if (strpos($style->src, $plugin_slug) !== false) {
$css_count++;
}
}
// بررسی اسکریپتها
foreach ($wp_scripts->registered as $handle => $script) {
if (strpos($script->src, $plugin_slug) !== false) {
$js_count++;
}
}
// ذخیره اطلاعات
$this->data['plugins'][$plugin_path] = array(
'name' => $plugin_data['Name'],
'version' => $plugin_data['Version'],
'css_count' => $css_count,
'js_count' => $js_count
);
}
}
}
$collectors['plugin_impact'] = new QM_Collector_Plugin_Impact();
}
return $collectors;
}
add_filter('qm/collectors', 'add_custom_qm_collectors');
2. تست عملکرد با فعال/غیرفعال کردن افزونهها
برای تست دقیقتر، میتوانید از اسکریپت زیر برای فعال/غیرفعال کردن موقت افزونهها استفاده کنید:
PHP// اضافه کردن این کد به یک فایل PHP جداگانه (plugin-tester.php) در ریشه وردپرس
// این فایل فقط باید برای تست استفاده شود و پس از آن حذف شود
<?php
require_once('wp-load.php');
// بررسی امنیت
if (!current_user_can('administrator') || !isset($_GET['secret']) || $_GET['secret'] !== 'your_secret_key') {
die('Unauthorized access');
}
$action = isset($_GET['action']) ? $_GET['action'] : '';
$plugin = isset($_GET['plugin']) ? $_GET['plugin'] : '';
$redirect = isset($_GET['redirect']) ? $_GET['redirect'] : home_url();
if (!empty($plugin)) {
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
if ($action === 'disable') {
// غیرفعال کردن افزونه
deactivate_plugins($plugin);
echo "Plugin {$plugin} disabled temporarily. <a href='{$redirect}'>View site</a>";
} elseif ($action === 'enable') {
// فعال کردن افزونه
activate_plugin($plugin);
echo "Plugin {$plugin} enabled. <a href='{$redirect}'>View site</a>";
}
} else {
// نمایش لیست افزونههای فعال
$active_plugins = get_option('active_plugins');
echo '<h2>Active Plugins</h2>';
echo '<ul>';
foreach ($active_plugins as $plugin) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
echo '<li>' . $plugin_data['Name'] . ' (' . $plugin . ') -
<a href="?action=disable&plugin=' . urlencode($plugin) . '&secret=your_secret_key&redirect=' . urlencode($redirect) . '">Disable</a></li>';
}
echo '</ul>';
}
?>
برای استفاده از این اسکریپت، به آدرس https://your-site.com/plugin-tester.php?secret=your_secret_key
مراجعه کنید.
3. استفاده از Asset CleanUp یا Perfmatters
افزونههایی مانند Asset CleanUp یا Perfmatters به شما امکان میدهند فایلهای CSS و JS هر افزونه را در صفحات مختلف غیرفعال کنید.
مدیریت بهینه افزونهها
1. بارگذاری شرطی افزونهها
میتوانید افزونهها را فقط در صفحاتی که به آنها نیاز دارید، فعال کنید:
PHPfunction conditionally_load_plugins() {
// غیرفعال کردن افزونهها در صفحات خاص
if (!is_admin()) {
// لیست افزونههایی که میخواهید مدیریت کنید
$plugins_to_manage = array(
'contact-form-7/wp-contact-form-7.php' => array('contact', 'about'), // فقط در صفحات تماس و درباره ما فعال باشد
'woocommerce/woocommerce.php' => array('shop', 'product', 'cart', 'checkout'), // فقط در صفحات فروشگاهی فعال باشد
'elementor/elementor.php' => array('home', 'landing'), // فقط در صفحه اصلی و صفحات فرود فعال باشد
);
// صفحه فعلی
$current_page = '';
if (is_page()) {
$current_page = get_post_field('post_name', get_the_ID());
} elseif (is_single()) {
$current_page = 'single';
} elseif (is_home() || is_front_page()) {
$current_page = 'home';
} elseif (is_archive()) {
$current_page = 'archive';
} elseif (is_search()) {
$current_page = 'search';
}
// بررسی هر افزونه
foreach ($plugins_to_manage as $plugin => $allowed_pages) {
// اگر صفحه فعلی در لیست صفحات مجاز نیست، اسکریپتها و استایلهای افزونه را غیرفعال کن
if (!in_array($current_page, $allowed_pages)) {
// استخراج نام افزونه از مسیر
$plugin_name = explode('/', $plugin)[0];
// حذف اسکریپتها و استایلهای مرتبط با افزونه
add_action('wp_enqueue_scripts', function() use ($plugin_name) {
global $wp_styles, $wp_scripts;
// حذف استایلها
foreach ($wp_styles->registered as $handle => $style) {
if (strpos($handle, $plugin_name) !== false || (isset($style->src) && strpos($style->src, $plugin_name) !== false)) {
wp_dequeue_style($handle);
}
}
// حذف اسکریپتها
foreach ($wp_scripts->registered as $handle => $script) {
if (strpos($handle, $plugin_name) !== false || (isset($script->src) && strpos($script->src, $plugin_name) !== false)) {
wp_dequeue_script($handle);
}
}
}, 100);
}
}
}
}
add_action('wp', 'conditionally_load_plugins');
2. مدیریت اسکریپتها و استایلهای افزونهها
برخی افزونهها اسکریپتها و استایلهای خود را در تمام صفحات بارگذاری میکنند، حتی اگر نیازی به آنها نباشد:
PHPfunction manage_plugin_assets() {
// غیرفعال کردن اسکریپتهای Contact Form 7 در صفحاتی که فرم تماس ندارند
if (!is_page('contact') && !has_shortcode(get_the_content(), 'contact-form-7')) {
wp_dequeue_script('contact-form-7');
wp_dequeue_style('contact-form-7');
}
// غیرفعال کردن اسکریپتهای WooCommerce در صفحات غیر فروشگاهی
if (!is_woocommerce() && !is_cart() && !is_checkout() && !is_account_page()) {
wp_dequeue_script('woocommerce');
wp_dequeue_script('wc-cart-fragments');
wp_dequeue_script('wc-add-to-cart');
wp_dequeue_style('woocommerce-general');
wp_dequeue_style('woocommerce-layout');
}
// غیرفعال کردن اسکریپتهای WPML در صفحات خاص
if (!is_page(array('about', 'contact'))) {
wp_dequeue_script('wpml-browser-redirect');
wp_dequeue_style('wpml-legacy-dropdown-0');
}
// غیرفعال کردن اسکریپتهای شبکههای اجتماعی در صفحات خاص
if (is_page('checkout') || is_page('thank-you')) {
wp_dequeue_script('social-share');
wp_dequeue_style('social-share-icons');
}
}
add_action('wp_enqueue_scripts', 'manage_plugin_assets', 100);
3. ترکیب و مینیفای کردن فایلهای افزونهها
برای کاهش تعداد درخواستها، میتوانید فایلهای افزونهها را ترکیب و مینیفای کنید:
PHPfunction combine_plugin_styles() {
// لیست استایلهایی که میخواهید ترکیب کنید
$styles_to_combine = array(
'plugin-1-style',
'plugin-2-style',
'plugin-3-style'
);
// حذف استایلهای اصلی
foreach ($styles_to_combine as $handle) {
wp_dequeue_style($handle);
}
// افزودن استایل ترکیبی
wp_enqueue_style('combined-plugin-styles', get_template_directory_uri() . '/assets/css/combined-plugins.css', array(), '1.0');
}
add_action('wp_enqueue_scripts', 'combine_plugin_styles', 999);
function combine_plugin_scripts() {
// لیست اسکریپتهایی که میخواهید ترکیب کنید
$scripts_to_combine = array(
'plugin-1-script',
'plugin-2-script',
'plugin-3-script'
);
// حذف اسکریپتهای اصلی
foreach ($scripts_to_combine as $handle) {
wp_dequeue_script($handle);
}
// افزودن اسکریپت ترکیبی
wp_enqueue_script('combined-plugin-scripts', get_template_directory_uri() . '/assets/js/combined-plugins.js', array('jquery'), '1.0', true);
}
add_action('wp_enqueue_scripts', 'combine_plugin_scripts', 999);
برای ایجاد فایلهای ترکیبی، میتوانید از اسکریپت زیر استفاده کنید:
PHP// اضافه کردن این کد به یک فایل PHP جداگانه و اجرای آن یک بار
function create_combined_assets() {
// ترکیب استایلها
$styles_to_combine = array(
WP_PLUGIN_DIR . '/plugin-1/assets/css/style.css',
WP_PLUGIN_DIR . '/plugin-2/assets/css/style.css',
WP_PLUGIN_DIR . '/plugin-3/assets/css/style.css'
);
$combined_css = '';
foreach ($styles_to_combine as $file) {
if (file_exists($file)) {
$css = file_get_contents($file);
// اصلاح مسیرهای نسبی
$css = preg_replace_callback('/url\([\'"]?([^\'")]+)[\'"]?\)/', function($matches) use ($file) {
$url = $matches[1];
if (substr($url, 0, 1) === '/' || substr($url, 0, 4) === 'http') {
return "url({$url})"; // URL مطلق است، تغییر نده
}
$file_dir = dirname($file);
$absolute_url = realpath($file_dir . '/' . $url);
if ($absolute_url) {
$wp_upload_dir = wp_upload_dir();
$relative_url = str_replace(ABSPATH, '/', $absolute_url);
return "url({$relative_url})";
}
return $matches[0];
}, $css);
$combined_css .= "/* Source: " . basename($file) . " */\n" . $css . "\n\n";
}
}
// مینیفای کردن CSS
$combined_css = preg_replace('/\s+/', ' ', $combined_css);
$combined_css = preg_replace('/\/\*(?:(?!\*\/).)*\*\//', '', $combined_css);
$combined_css = str_replace(': ', ':', $combined_css);
$combined_css = str_replace('{ ', '{', $combined_css);
$combined_css = str_replace(' }', '}', $combined_css);
$combined_css = str_replace('; ', ';', $combined_css);
// ذخیره فایل ترکیبی
$target_dir = get_template_directory() . '/assets/css/';
if (!file_exists($target_dir)) {
mkdir($target_dir, 0755, true);
}
file_put_contents($target_dir . 'combined-plugins.css', $combined_css);
// ترکیب اسکریپتها
$scripts_to_combine = array(
WP_PLUGIN_DIR . '/plugin-1/assets/js/script.js',
WP_PLUGIN_DIR . '/plugin-2/assets/js/script.js',
WP_PLUGIN_DIR . '/plugin-3/assets/js/script.js'
);
$combined_js = '';
foreach ($scripts_to_combine as $file) {
if (file_exists($file)) {
$js = file_get_contents($file);
$combined_js .= "/* Source: " . basename($file) . " */\n" . $js . ";\n\n";
}
}
// مینیفای کردن JS (ساده)
$combined_js = preg_replace('/(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/m', '', $combined_js);
$combined_js = preg_replace('/\s+/', ' ', $combined_js);
// ذخیره فایل ترکیبی
$target_dir = get_template_directory() . '/assets/js/';
if (!file_exists($target_dir)) {
mkdir($target_dir, 0755, true);
}
file_put_contents($target_dir . 'combined-plugins.js', $combined_js);
echo "Combined files created successfully!";
}
// اجرای تابع
create_combined_assets();
افزونههای مفید برای بهبود Core Web Vitals
1. افزونههای کش و بهینهسازی
افزونههای کش وردپرس نقش مهمی در بهبود Core Web Vitals دارند:
- WP Rocket: یک افزونه جامع کش و بهینهسازی
- LiteSpeed Cache: برای سرورهای LiteSpeed با امکانات پیشرفته
- W3 Total Cache: افزونه رایگان با امکانات گسترده
- WP Super Cache: راهحل ساده و سبک برای کش صفحه
2. افزونههای بهینهسازی تصاویر
بهینهسازی تصاویر وردپرس با استفاده از افزونههای زیر:
- ShortPixel: فشردهسازی هوشمند تصاویر با حفظ کیفیت
- EWWW Image Optimizer: بهینهسازی خودکار تصاویر هنگام آپلود
- Imagify: فشردهسازی تصاویر با سه سطح مختلف کیفیت
- WebP Express: تبدیل خودکار تصاویر به فرمت WebP
3. افزونههای بهینهسازی کد
افزونههای زیر به بهینهسازی CSS و JavaScript کمک میکنند:
- Autoptimize: ترکیب، مینیفای و کش کردن CSS و JavaScript
- Async JavaScript: کنترل دقیق بر نحوه بارگذاری اسکریپتها
- Perfmatters: کنترل دقیق بر اسکریپتها و استایلها در هر صفحه
- Asset CleanUp: غیرفعال کردن فایلهای CSS و JS غیرضروری در صفحات خاص
4. افزونههای مدیریت فونت
بهینهسازی فونت در وردپرس با استفاده از افزونههای زیر:
- OMGF (Host Google Fonts Locally): میزبانی فونتهای گوگل به صورت محلی
- Fonts Plugin | Google Fonts Typography: مدیریت و بهینهسازی فونتهای گوگل
- WP Fonts: بارگذاری بهینه فونتها با استفاده از font-display: swap
جدول ۱۰: مقایسه افزونههای بهینهسازی Core Web Vitals﷼
افزونه | نوع | سازگاری با Core Web Vitals | قیمت WP |
---|---|---|---|
WP Rocket | کش و بهینهسازی | بسیار زیاد | از $49 |
ShortPixel | بهینهسازی تصویر | زیاد | رایگان/پریمیوم |
Autoptimize | بهینهسازی کد | زیاد | رایگان |
Perfmatters | بهینهسازی کد | بسیار زیاد | از $24.95 |
OMGF | مدیریت فونت | متوسط | رایگان/پریمیوم |
Asset CleanUp | مدیریت اسکریپت | زیاد | رایگان/پریمیوم |
Query Monitor | تشخیص مشکل | کمک به شناسایی | رایگان |
نکات حیاتی در مدیریت افزونهها
- اصل کمتر، بهتر: هر افزونه اضافی میتواند بار اضافی به سایت تحمیل کند. فقط افزونههای ضروری را نصب کنید.
- بررسی منظم افزونهها: هر چند ماه یک بار، افزونههای خود را بررسی کنید و موارد غیرضروری را حذف کنید.
- بهروزرسانی منظم: افزونههای بهروز معمولاً عملکرد بهتری دارند و مشکلات امنیتی کمتری ایجاد میکنند.
- جایگزینی افزونههای سنگین: افزونههای چندمنظوره سنگین را با نمونههای سبکتر و تخصصیتر جایگزین کنید.
- استفاده از کد سفارشی به جای افزونه: برای عملکردهای ساده، به جای نصب افزونه، میتوانید از قطعات کد سفارشی استفاده کنید.
مدیریت صحیح افزونهها یکی از مهمترین جنبههای بهینهسازی وردپرس برای Core Web Vitals است. با استفاده از تکنیکهای فوق، میتوانید تأثیر منفی افزونهها بر عملکرد سایت را به حداقل برسانید و به امتیاز 100 در Lighthouse نزدیکتر شوید.
در بخش بعدی، به بررسی روشهای بهینهسازی دیتابیس وردپرس خواهیم پرداخت که تأثیر مستقیمی بر TTFB و در نتیجه LCP دارد.
12. بهینهسازی دیتابیس وردپرس برای بهبود Core Web Vitals
بهینهسازی دیتابیس وردپرس یکی از جنبههای مهم اما اغلب نادیده گرفته شده در مسیر بهبود Core Web Vitals است. دیتابیس وردپرس با گذشت زمان و افزودن محتوا، افزونهها و تنظیمات، میتواند حجیم و ناکارآمد شود. این موضوع مستقیماً بر TTFB (Time To First Byte) تأثیر میگذارد که پیشنیاز بهبود LCP است. در این بخش، تکنیکهای پیشرفته برای بهینهسازی دیتابیس وردپرس را بررسی میکنیم.
شناسایی مشکلات دیتابیس
1. استفاده از Query Monitor برای شناسایی کوئریهای کند
افزونه Query Monitor به شما امکان میدهد کوئریهای کند و مشکلدار را شناسایی کنید:
PHP// افزودن فیلتر سفارشی به Query Monitor برای هشدار کوئریهای کند
function add_slow_query_warning($query) {
if ($query->time > 0.05) { // هشدار برای کوئریهای بیش از 50 میلیثانیه
$query->warning = sprintf('Slow query: %s seconds', number_format($query->time, 4));
}
return $query;
}
add_filter('qm/collect/db_query', 'add_slow_query_warning');
2. بررسی اندازه جداول دیتابیس
برای بررسی اندازه جداول دیتابیس، میتوانید از کد زیر استفاده کنید:
PHPfunction check_database_size() {
if (!current_user_can('manage_options') || !isset($_GET['check_db'])) return;
global $wpdb;
$tables = $wpdb->get_results("SHOW TABLE STATUS", ARRAY_A);
$total_size = 0;
echo '<div style="background: #fff; padding: 20px; margin: 20px; border: 1px solid #ccc;">';
echo '<h2>Database Tables Size</h2>';
echo '<table style="width: 100%; border-collapse: collapse;">';
echo '<tr><th style="text-align: left; border: 1px solid #ccc; padding: 5px;">Table</th><th style="text-align: left; border: 1px solid #ccc; padding: 5px;">Size (MB)</th><th style="text-align: left; border: 1px solid #ccc; padding: 5px;">Rows</th></tr>';
foreach ($tables as $table) {
$size = ($table['Data_length'] + $table['Index_length']) / (1024 * 1024);
$total_size += $size;
$is_wp_table = strpos($table['Name'], $wpdb->prefix) === 0;
$row_style = $is_wp_table ? '' : 'background-color: #f9f9f9;';
echo '<tr style="' . $row_style . '">';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . $table['Name'] . '</td>';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . number_format($size, 2) . ' MB</td>';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . number_format($table['Rows']) . '</td>';
echo '</tr>';
}
echo '<tr style="font-weight: bold; background-color: #eee;">';
echo '<td style="border: 1px solid #ccc; padding: 5px;">Total</td>';
echo '<td style="border: 1px solid #ccc; padding: 5px;">' . number_format($total_size, 2) . ' MB</td>';
echo '<td style="border: 1px solid #ccc; padding: 5px;"></td>';
echo '</tr>';
echo '</table>';
echo '</div>';
}
add_action('admin_notices', 'check_database_size');
برای استفاده از این کد، به پنل مدیریت وردپرس مراجعه کرده و ?check_db=1 را به URL اضافه کنید.
پاکسازی و بهینهسازی دیتابیس
1. حذف دادههای زائد
دیتابیس وردپرس با گذشت زمان پر از دادههای زائد میشود که میتوانید آنها را پاکسازی کنید:
PHPfunction cleanup_database() {
global $wpdb;
// حذف پیشنویسهای خودکار و بازبینیهای قدیمی
$wpdb->query("DELETE FROM $wpdb->posts WHERE post_status = 'auto-draft' OR post_status = 'revision'");
// حذف متادیتاهای بیاستفاده
$wpdb->query("DELETE pm FROM $wpdb->postmeta pm LEFT JOIN $wpdb->posts p ON pm.post_id = p.ID WHERE p.ID IS NULL");
// حذف دیدگاههای اسپم و زباله
$wpdb->query("DELETE FROM $wpdb->comments WHERE comment_approved = 'spam' OR comment_approved = 'trash'");
// حذف متادیتاهای دیدگاههای حذف شده
$wpdb->query("DELETE cm FROM $wpdb->commentmeta cm LEFT JOIN $wpdb->comments c ON cm.comment_id = c.comment_ID WHERE c.comment_ID IS NULL");
// حذف گزینههای موقت
$wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_%' OR option_name LIKE '_site_transient_%'");
// حذف لینکهای شکسته
$wpdb->query("DELETE FROM $wpdb->links WHERE link_visible = 'N'");
// حذف کاربران بدون نقش
$wpdb->query("
DELETE u, um
FROM $wpdb->users u
LEFT JOIN $wpdb->usermeta um ON u.ID = um.user_id
LEFT JOIN (
SELECT user_id
FROM $wpdb->usermeta
WHERE meta_key = '{$wpdb->prefix}capabilities'
) cap ON u.ID = cap.user_id
WHERE cap.user_id IS NULL
");
}
// اجرای خودکار هر هفته
if (!wp_next_scheduled('database_cleanup_event')) {
wp_schedule_event(time(), 'weekly', 'database_cleanup_event');
}
add_action('database_cleanup_event', 'cleanup_database');
// اضافه کردن دکمه پاکسازی دستی به پنل مدیریت
function add_cleanup_button() {
if (current_user_can('manage_options')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Clean up your database to improve performance. <a href="' . admin_url('tools.php?cleanup_db=1') . '" class="button button-primary">Clean Up Now</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_cleanup_button');
// پردازش درخواست پاکسازی دستی
function process_cleanup_request() {
if (current_user_can('manage_options') && isset($_GET['cleanup_db'])) {
cleanup_database();
// بهینهسازی جداول
global $wpdb;
$tables = $wpdb->get_results("SHOW TABLES LIKE '{$wpdb->prefix}%'", ARRAY_N);
foreach ($tables as $table) {
$wpdb->query("OPTIMIZE TABLE {$table[0]}");
}
wp_redirect(admin_url('tools.php?cleanup_success=1'));
exit;
}
}
add_action('admin_init', 'process_cleanup_request');
// نمایش پیام موفقیت
function show_cleanup_success() {
if (isset($_GET['cleanup_success'])) {
echo '<div class="notice notice-success is-dismissible">';
echo '<p>Database cleanup completed successfully!</p>';
echo '</div>';
}
}
add_action('admin_notices', 'show_cleanup_success');
2. بهینهسازی جداول دیتابیس
بهینهسازی منظم جداول دیتابیس میتواند عملکرد را بهبود بخشد:
PHPfunction optimize_database_tables() {
global $wpdb;
// لیست جداول وردپرس
$tables = $wpdb->get_results("SHOW TABLES LIKE '{$wpdb->prefix}%'", ARRAY_N);
foreach ($tables as $table) {
$table_name = $table[0];
// بررسی و تعمیر جدول
$wpdb->query("CHECK TABLE {$table_name}");
$wpdb->query("REPAIR TABLE {$table_name}");
// بهینهسازی جدول
$result = $wpdb->query("OPTIMIZE TABLE {$table_name}");
// لاگ نتیجه
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Optimized table {$table_name}: " . ($result ? 'Success' : 'Failed'));
}
}
}
// اجرای خودکار هر ماه
if (!wp_next_scheduled('optimize_database_event')) {
wp_schedule_event(time(), 'monthly', 'optimize_database_event');
}
add_action('optimize_database_event', 'optimize_database_tables');
3. کاهش تعداد ویرایشهای پست
وردپرس به طور پیشفرض تمام ویرایشهای پست را ذخیره میکند، که میتواند باعث بزرگ شدن دیتابیس شود:
PHP// محدود کردن تعداد ویرایشهای ذخیره شده به 5
if (!defined('WP_POST_REVISIONS')) {
define('WP_POST_REVISIONS', 5);
}
// یا غیرفعال کردن کامل ویرایشها
// define('WP_POST_REVISIONS', false);
// حذف ویرایشهای قدیمی
function delete_old_post_revisions() {
global $wpdb;
// حذف ویرایشهای قدیمیتر از 30 روز
$date = date('Y-m-d', strtotime('-30 days'));
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->posts WHERE post_type = 'revision' AND post_date < %s",
$date
)
);
}
add_action('wp_scheduled_delete', 'delete_old_post_revisions');
بهینهسازی کوئریهای دیتابیس
1. استفاده از کش برای کوئریهای پرکاربرد
کش کردن نتایج کوئریهای پرکاربرد میتواند بار دیتابیس را کاهش دهد:
PHPfunction get_popular_posts_cached($count = 5, $days = 30) {
// ساخت کلید کش
$cache_key = 'popular_posts_' . $count . '_' . $days;
// بررسی وجود داده در کش
$results = wp_cache_get($cache_key, 'popular_posts');
if ($results === false) {
global $wpdb;
// اجرای کوئری
$date = date('Y-m-d', strtotime('-' . $days . ' days'));
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT p.ID, p.post_title, COUNT(pm.meta_value) as view_count
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'post_views'
WHERE p.post_status = 'publish'
AND p.post_type = 'post'
AND p.post_date >= %s
GROUP BY p.ID
ORDER BY view_count DESC
LIMIT %d",
$date,
$count
)
);
// ذخیره در کش برای 12 ساعت
wp_cache_set($cache_key, $results, 'popular_posts', 12 * HOUR_IN_SECONDS);
}
return $results;
}
// مثال استفاده
function display_popular_posts() {
$popular_posts = get_popular_posts_cached(5, 30);
if ($popular_posts) {
echo '<ul>';
foreach ($popular_posts as $post) {
echo '<li><a href="' . get_permalink($post->ID) . '">' . $post->post_title . '</a></li>';
}
echo '</ul>';
}
}
2. بهینهسازی کوئریهای WP_Query
کوئریهای WP_Query نامناسب میتوانند بار زیادی به دیتابیس تحمیل کنند:
PHP// کوئری غیربهینه
$args = array(
'post_type' => 'post',
'posts_per_page' => -1, // بد: همه پستها
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'featured',
'value' => 'yes',
'compare' => '='
),
array(
'key' => 'rating',
'value' => 4,
'compare' => '>='
)
),
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => 'popular'
)
)
);
// کوئری بهینه
$args = array(
'post_type' => 'post',
'posts_per_page' => 10, // خوب: محدود کردن تعداد
'no_found_rows' => true, // خوب: غیرفعال کردن شمارش کل ردیفها
'update_post_meta_cache' => false, // خوب: غیرفعال کردن کش متادیتا (اگر نیاز ندارید)
'update_post_term_cache' => false, // خوب: غیرفعال کردن کش ترمها (اگر نیاز ندارید)
'fields' => 'ids', // خوب: فقط دریافت شناسهها (اگر فقط به شناسهها نیاز دارید)
'meta_key' => 'featured', // خوب: استفاده از meta_key به جای meta_query
'meta_value' => 'yes',
'meta_query' => array(
array(
'key' => 'rating',
'value' => 4,
'compare' => '>='
)
),
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'term_id', // خوب: استفاده از term_id به جای slug
'terms' => 15
)
)
);
3. ایجاد ایندکسهای مناسب
ایجاد ایندکسهای مناسب میتواند سرعت کوئریها را افزایش دهد:
PHPfunction add_custom_indexes() {
global $wpdb;
// بررسی وجود ایندکسها
$check_postmeta_index = $wpdb->get_results("SHOW INDEX FROM {$wpdb->postmeta} WHERE Key_name = 'meta_key_value'");
if (empty($check_postmeta_index)) {
// ایجاد ایندکس ترکیبی برای meta_key و meta_value
$wpdb->query("ALTER TABLE {$wpdb->postmeta} ADD INDEX meta_key_value (meta_key(191), meta_value(191))");
}
// ایندکس برای جستجوی پستها
$check_posts_index = $wpdb->get_results("SHOW INDEX FROM {$wpdb->posts} WHERE Key_name = 'post_title_content'");
if (empty($check_posts_index)) {
// ایجاد ایندکس برای جستجو در عنوان و محتوا
$wpdb->query("ALTER TABLE {$wpdb->posts} ADD FULLTEXT post_title_content (post_title, post_content)");
}
}
// اجرا در زمان فعالسازی افزونه یا تم
register_activation_hook(__FILE__, 'add_custom_indexes');
// یا اجرا یک بار به صورت دستی
if (isset($_GET['add_db_indexes']) && current_user_can('manage_options')) {
add_custom_indexes();
wp_die('Indexes added successfully!');
}
کاهش درخواستهای دیتابیس
1. استفاده از Object Cache
کش کردن اشیاء میتواند تعداد درخواستهای دیتابیس را کاهش دهد:
PHP// استفاده از object cache برای ذخیره دادههای پرکاربرد
function get_site_settings() {
$settings = wp_cache_get('site_settings', 'settings');
if ($settings === false) {
// دریافت تنظیمات از دیتابیس
$settings = array(
'logo' => get_option('site_logo'),
'phone' => get_option('contact_phone'),
'email' => get_option('contact_email'),
'address' => get_option('company_address'),
'social_links' => get_option('social_media_links'),
'copyright' => get_option('copyright_text'),
'analytics_id' => get_option('analytics_id')
);
// ذخیره در کش
wp_cache_set('site_settings', $settings, 'settings', 12 * HOUR_IN_SECONDS);
}
return $settings;
}
// استفاده در تم
function display_site_footer() {
$settings = get_site_settings();
echo '<footer>';
echo '<div class="logo"><img src="' . esc_url($settings['logo']) . '" alt="Logo"></div>';
echo '<div class="contact">';
echo '<p>Phone: ' . esc_html($settings['phone']) . '</p>';
echo '<p>Email: ' . esc_html($settings['email']) . '</p>';
echo '<p>Address: ' . esc_html($settings['address']) . '</p>';
echo '</div>';
echo '<div class="copyright">' . esc_html($settings['copyright']) . '</div>';
echo '</footer>';
}
2. پیشگیری از کوئریهای N+1
کوئریهای N+1 یکی از رایجترین مشکلات عملکردی در وردپرس هستند:
PHP// بد: کوئری N+1
$posts = get_posts(array('posts_per_page' => 10));
foreach ($posts as $post) {
$categories = get_the_category($post->ID); // یک کوئری اضافی برای هر پست
$tags = get_the_tags($post->ID); // یک کوئری اضافی دیگر برای هر پست
$author = get_the_author_meta('display_name', $post->post_author); // یک کوئری اضافی دیگر
// نمایش پست
}
// خوب: استفاده از update_post_term_cache و update_post_meta_cache
$posts = get_posts(array(
'posts_per_page' => 10,
'update_post_term_cache' => true, // پیشبارگذاری ترمها
'update_post_meta_cache' => true // پیشبارگذاری متادیتا
));
// پیشبارگذاری دادههای نویسندگان
$author_ids = wp_list_pluck($posts, 'post_author');
$author_ids = array_unique($author_ids);
if (!empty($author_ids)) {
// یک کوئری برای دریافت تمام نویسندگان
$authors = get_users(array(
'include' => $author_ids,
'fields' => array('ID', 'display_name')
));
// ایجاد آرایه برای دسترسی سریع
$author_data = array();
foreach ($authors as $author) {
$author_data[$author->ID] = $author->display_name;
}
}
foreach ($posts as $post) {
$categories = get_the_category($post->ID); // حالا از کش استفاده میکند
$tags = get_the_tags($post->ID); // حالا از کش استفاده میکند
$author = isset($author_data[$post->post_author]) ? $author_data[$post->post_author] : '';
// نمایش پست
}
3. استفاده از کوئریهای سفارشی برای عملیات پیچیده
گاهی اوقات، نوشتن کوئریهای SQL سفارشی میتواند کارآمدتر از استفاده از API وردپرس باشد:
PHPfunction get_popular_products_by_category($category_id, $limit = 5) {
global $wpdb;
// کوئری سفارشی برای دریافت محصولات پرفروش در یک دسته
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT p.ID, p.post_title, pm.meta_value as sales
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'total_sales'
WHERE p.post_type = 'product'
AND p.post_status = 'publish'
AND tt.taxonomy = 'product_cat'
AND tt.term_id = %d
ORDER BY pm.meta_value+0 DESC
LIMIT %d",
$category_id,
$limit
)
);
return $results;
}
جدول ۱۱: تأثیر تکنیکهای بهینهسازی دیتابیس بر TTFB
تکنیک | بهترین برای | پیچیدگی پیادهسازی | تأثیر بر TTFB |
---|---|---|---|
پاکسازی دیتابیس | تمام سایتها | کم | متوسط |
بهینهسازی جداول | دیتابیسهای بزرگ | کم | متوسط |
کش کردن کوئریها | سایتهای با ترافیک بالا | متوسط | زیاد |
ایجاد ایندکس | کوئریهای پیچیده | زیاد | متوسط |
بهینهسازی WP_Query | صفحات آرشیو و لیست | متوسط | زیاد |
جلوگیری از N+1 کوئریها | صفحات با لیست محتوا | بسیار زیاد | زیاد |
کوئریهای سفارشی | عملیات پیچیده دیتابیس | زیاد | زیاد |
بهینهسازی دیتابیس وردپرس میتواند TTFB را تا 50% کاهش دهد، که تأثیر مستقیمی بر LCP و در نتیجه Core Web Vitals دارد. با استفاده از تکنیکهای فوق، میتوانید عملکرد دیتابیس را بهبود بخشیده و به امتیاز 100 در Lighthouse نزدیکتر شوید.
در بخش بعدی، به بررسی استفاده از CDN برای بهبود Core Web Vitals خواهیم پرداخت، که یکی از موثرترین راههای کاهش زمان بارگذاری است.
13. استفاده از CDN برای بهبود چشمگیر Core Web Vitals
CDN برای وردپرس (Content Delivery Network) یکی از موثرترین راههای بهبود Core Web Vitals است. CDN شبکهای از سرورها در نقاط مختلف جهان است که محتوای شما را ذخیره و به کاربران از نزدیکترین موقعیت جغرافیایی ارائه میدهد. این باعث کاهش چشمگیر زمان بارگذاری، بهبود LCP و کاهش TTFB میشود. در این بخش، به بررسی نحوه پیادهسازی CDN در وردپرس، مقایسه سرویسهای مختلف و تنظیمات بهینه میپردازیم.
مزایای استفاده از CDN برای Core Web Vitals
- کاهش TTFB: با ارائه محتوا از سرور نزدیک به کاربر، TTFB به طور قابل توجهی کاهش مییابد.
- بهبود LCP: فایلهای بزرگ مانند تصاویر با سرعت بیشتری بارگذاری میشوند.
- کاهش بار سرور اصلی: CDN بخش زیادی از ترافیک را از سرور اصلی منتقل میکند.
- امنیت بیشتر: بسیاری از CDNها امکانات امنیتی مانند WAF و محافظت DDoS ارائه میدهند.
- پشتیبانی از HTTP/2 و HTTP/3: بسیاری از CDNها از پروتکلهای جدید پشتیبانی میکنند.
انواع CDN و مقایسه آنها
جدول ۱۲: مقایسه سرویسهای CDN محبوب برای وردپرس
CDN | نوع | ویژگیهای کلیدی | قیمت | یکپارچگی با وردپرس |
---|---|---|---|---|
Cloudflare | Full-Stack | WAF، بهینهسازی تصویر، HTTP/3، کش | رایگان/پریمیوم | عالی |
BunnyCDN | Pull/Push | قیمت مناسب، بهینهسازی تصویر، سرعت بالا | از $1/ماه | خوب |
KeyCDN | Pull/Push | API ساده، قیمت مقرونبهصرفه، HTTP/2 | از $0.04/GB | خوب |
StackPath | Pull | کامل API، امنیت پیشرفته، Edge Rules | از $10/ماه | خوب |
Amazon CloudFront | Pull/Push | مقیاسپذیری عالی، AWS Lambda@Edge | $0.085/GB | متوسط |
Google Cloud CDN | Pull | گسترش جهانی، یکپارچگی با Google Cloud | از $0.08/GB | متوسط |
پیادهسازی CDN در وردپرس
1. پیادهسازی Cloudflare
Cloudflare یکی از محبوبترین CDNها برای وردپرس است:
PHP// اضافه کردن هدرهای Cloudflare به .htaccess
function add_cloudflare_htaccess() {
$htaccess_file = ABSPATH . '.htaccess';
if (file_exists($htaccess_file) && is_writable($htaccess_file)) {
$htaccess_content = file_get_contents($htaccess_file);
// بررسی وجود قبلی کد
if (strpos($htaccess_content, '# BEGIN Cloudflare') === false) {
$cloudflare_rules = '
# BEGIN Cloudflare
<IfModule mod_headers.c>
# اطمینان از شناسایی صحیح IP کاربران
SetEnvIf CF-Connecting-IP "^(.*)$" REMOTE_ADDR=$1
# اضافه کردن هدرهای CORS برای منابع استاتیک
<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|font.css|css|js|gif|png|jpe?g|svg|webp|mp4|webm)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
# هدرهای بهینهسازی کش
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico|css|js|woff2|woff|ttf)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>
# تنظیمات Cloudflare Rocket Loader
<IfModule mod_headers.c>
<FilesMatch "\.js$">
Header set CF-JS-Load "defer"
</FilesMatch>
</IfModule>
# END Cloudflare
';
// اضافه کردن قوانین به ابتدای فایل
$htaccess_content = preg_replace('/^(# BEGIN WordPress.*)$/m', $cloudflare_rules . '$1', $htaccess_content);
file_put_contents($htaccess_file, $htaccess_content);
}
}
}
// اجرا در زمان فعالسازی تم یا افزونه
register_activation_hook(__FILE__, 'add_cloudflare_htaccess');
// تشخیص خودکار Cloudflare
function is_cloudflare() {
return isset($_SERVER['HTTP_CF_CONNECTING_IP']);
}
// اصلاح IP کاربر در وردپرس هنگام استفاده از Cloudflare
function fix_cloudflare_ip($ip) {
if (is_cloudflare() && isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
return $ip;
}
add_filter('pre_comment_user_ip', 'fix_cloudflare_ip');
2. پیادهسازی CDN Pull با استفاده از افزونه یا کد سفارشی
CDN Pull به سادگی URLهای فایلهای استاتیک را با URLهای CDN جایگزین میکند:
PHPfunction replace_urls_with_cdn($content) {
$cdn_url = 'https://cdn.example.com'; // URL سرویس CDN شما
$site_url = site_url();
// جایگزینی URLهای استاتیک با CDN
$content = str_replace(
array(
$site_url . '/wp-content/uploads',
$site_url . '/wp-content/themes',
$site_url . '/wp-content/plugins',
$site_url . '/wp-includes'
),
array(
$cdn_url . '/wp-content/uploads',
$cdn_url . '/wp-content/themes',
$cdn_url . '/wp-content/plugins',
$cdn_url . '/wp-includes'
),
$content
);
return $content;
}
add_filter('the_content', 'replace_urls_with_cdn');
add_filter('widget_text', 'replace_urls_with_cdn');
add_filter('wp_calculate_image_srcset', 'replace_urls_with_cdn');
// جایگزینی URLها در CSS و JS
function cdn_enqueue_scripts() {
global $wp_styles, $wp_scripts;
$cdn_url = 'https://cdn.example.com'; // URL سرویس CDN شما
$site_url = site_url();
// جایگزینی URLها در CSS
foreach ($wp_styles->registered as $handle => $style) {
if (isset($style->src) && strpos($style->src, $site_url) === 0) {
$wp_styles->registered[$handle]->src = str_replace($site_url, $cdn_url, $style->src);
}
}
// جایگزینی URLها در JS
foreach ($wp_scripts->registered as $handle => $script) {
if (isset($script->src) && strpos($script->src, $site_url) === 0) {
$wp_scripts->registered[$handle]->src = str_replace($site_url, $cdn_url, $script->src);
}
}
}
add_action('wp_enqueue_scripts', 'cdn_enqueue_scripts', 999);
// جایگزینی URLها در srcset تصاویر
function cdn_image_srcset($sources) {
$cdn_url = 'https://cdn.example.com'; // URL سرویس CDN شما
$site_url = site_url();
if (is_array($sources)) {
foreach ($sources as &$source) {
if (isset($source['url']) && strpos($source['url'], $site_url) === 0) {
$source['url'] = str_replace($site_url, $cdn_url, $source['url']);
}
}
}
return $sources;
}
add_filter('wp_calculate_image_srcset', 'cdn_image_srcset', 10, 1);
3. پیادهسازی CDN Push
CDN Push نیاز به ارسال فایلها به CDN دارد. این کار معمولاً با استفاده از افزونهها یا هوکهای وردپرس انجام میشود:
PHP// ارسال فایلهای آپلود شده به CDN
function push_uploads_to_cdn($attachment_id) {
$file = get_attached_file($attachment_id);
if (!$file) return;
// دریافت اطلاعات فایل
$filename = basename($file);
$upload_dir = wp_upload_dir();
$file_path = str_replace($upload_dir['basedir'], '', $file);
// ارسال فایل اصلی به CDN
push_file_to_cdn($file, $file_path);
// ارسال سایزهای مختلف تصویر به CDN
$metadata = wp_get_attachment_metadata($attachment_id);
if (isset($metadata['sizes']) && is_array($metadata['sizes'])) {
$dir = dirname($file) . '/';
foreach ($metadata['sizes'] as $size => $sizeinfo) {
$sized_file = $dir . $sizeinfo['file'];
$sized_path = dirname($file_path) . '/' . $sizeinfo['file'];
push_file_to_cdn($sized_file, $sized_path);
}
}
}
add_action('add_attachment', 'push_uploads_to_cdn');
add_action('edit_attachment', 'push_uploads_to_cdn');
// تابع ارسال فایل به CDN (مثال برای BunnyCDN)
function push_file_to_cdn($local_file, $remote_path) {
$cdn_api_key = 'your_api_key';
$cdn_storage_zone = 'your_storage_zone';
$cdn_region = 'de'; // منطقه ذخیرهسازی (de, ny, sg, ...)
$remote_url = "https://storage.bunnycdn.com/{$cdn_storage_zone}{$remote_path}";
$ch = curl_init($remote_url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"AccessKey: {$cdn_api_key}",
"Content-Type: application/octet-stream"
));
curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents($local_file));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// لاگ کردن نتیجه
if ($http_code >= 200 && $http_code < 300) {
error_log("File {$local_file} successfully pushed to CDN");
return true;
} else {
error_log("Failed to push file {$local_file} to CDN. HTTP Code: {$http_code}, Response: {$response}");
return false;
}
}
// همگامسازی تمام فایلها با CDN
function sync_all_files_with_cdn() {
if (!current_user_can('manage_options')) return;
// دریافت تمام تصاویر
$args = array(
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => -1,
'post_mime_type' => 'image'
);
$images = get_posts($args);
foreach ($images as $image) {
push_uploads_to_cdn($image->ID);
}
// همگامسازی فایلهای تم
$theme_dir = get_template_directory();
$theme_url = get_template_directory_uri();
$site_url = site_url();
$theme_relative_path = str_replace($site_url, '', $theme_url);
// ارسال فایلهای CSS و JS تم
sync_directory_with_cdn($theme_dir . '/assets/css', $theme_relative_path . '/assets/css');
sync_directory_with_cdn($theme_dir . '/assets/js', $theme_relative_path . '/assets/js');
sync_directory_with_cdn($theme_dir . '/assets/images', $theme_relative_path . '/assets/images');
wp_die('All files synced with CDN!');
}
// اضافه کردن دکمه همگامسازی به پنل مدیریت
function add_cdn_sync_button() {
if (current_user_can('manage_options')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Sync all files with CDN: <a href="' . admin_url('tools.php?sync_cdn=1') . '" class="button button-primary">Sync Now</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_cdn_sync_button');
// پردازش درخواست همگامسازی
function process_cdn_sync_request() {
if (current_user_can('manage_options') && isset($_GET['sync_cdn'])) {
sync_all_files_with_cdn();
}
}
add_action('admin_init', 'process_cdn_sync_request');
// همگامسازی یک دایرکتوری با CDN
function sync_directory_with_cdn($local_dir, $remote_path) {
if (!is_dir($local_dir)) return;
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($local_dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if ($file->isFile()) {
$file_path = $file->getPathname();
$relative_path = str_replace($local_dir, '', $file_path);
push_file_to_cdn($file_path, $remote_path . $relative_path);
}
}
}
تنظیمات بهینه CDN برای Core Web Vitals
1. فعالسازی HTTP/2 و HTTP/3
PHP// اضافه کردن هدرهای HTTP/2 Server Push
function http2_resource_hints() {
if (is_cloudflare() || is_cdn_enabled()) {
// پیشبارگذاری منابع کلیدی
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/css/main.css" as="style">';
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/js/main.js" as="script">';
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin>';
// اتصال زودهنگام به CDN
echo '<link rel="preconnect" href="https://cdn.example.com" crossorigin>';
// پیشبارگذاری DNS
echo '<link rel="dns-prefetch" href="https://cdn.example.com">';
}
}
add_action('wp_head', 'http2_resource_hints', 1);
2. بهینهسازی تصاویر با CDN
بسیاری از CDNها امکان بهینهسازی خودکار تصاویر را ارائه میدهند:
PHP// استفاده از قابلیتهای بهینهسازی تصویر Cloudflare
function optimize_images_with_cloudflare($image_url) {
// اگر Cloudflare Polish فعال است
if (is_cloudflare()) {
// تبدیل به WebP برای مرورگرهای پشتیبانیکننده
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false) {
$image_url = add_query_arg('format', 'webp', $image_url);
}
// بهینهسازی خودکار کیفیت تصویر
$image_url = add_query_arg('quality', 'auto', $image_url);
// تغییر اندازه تصویر برای دستگاههای مختلف
if (wp_is_mobile()) {
$image_url = add_query_arg('width', '768', $image_url);
}
}
return $image_url;
}
add_filter('wp_get_attachment_url', 'optimize_images_with_cloudflare', 100);
add_filter('wp_calculate_image_srcset_meta', 'optimize_images_with_cloudflare', 100);
3. تنظیم هدرهای کش مناسب
PHP// تنظیم هدرهای کش مناسب برای انواع فایلها
function set_cache_headers() {
$file = $_SERVER['REQUEST_URI'];
$ext = pathinfo($file, PATHINFO_EXTENSION);
// تنظیم Cache-Control برای انواع فایلها
$cache_extensions = array(
// فایلهای استاتیک با کش طولانیمدت
'css' => 'public, max-age=31536000, immutable',
'js' => 'public, max-age=31536000, immutable',
'jpg' => 'public, max-age=31536000, immutable',
'jpeg' => 'public, max-age=31536000, immutable',
'png' => 'public, max-age=31536000, immutable',
'gif' => 'public, max-age=31536000, immutable',
'webp' => 'public, max-age=31536000, immutable',
'svg' => 'public, max-age=31536000, immutable',
'ico' => 'public, max-age=31536000, immutable',
'woff' => 'public, max-age=31536000, immutable',
'woff2' => 'public, max-age=31536000, immutable',
// فایلهای HTML با کش کوتاهمدت
'html' => '
max-age=300, must-revalidate'
);
if (isset($cache_extensions[$ext])) {
header('Cache-Control: ' . $cache_extensions[$ext]);
}
}
add_action('init', 'set_cache_headers');
4. استفاده از CDN برای فایلهای خاص
PHP// استفاده از CDNهای تخصصی برای انواع خاص فایلها
function use_specialized_cdns($url) {
$file_extension = pathinfo($url, PATHINFO_EXTENSION);
// استفاده از CDN تخصصی برای تصاویر
if (in_array($file_extension, array('jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'))) {
return str_replace('https://example.com', 'https://images.example.com', $url);
}
// استفاده از CDN تخصصی برای فایلهای استاتیک
if (in_array($file_extension, array('css', 'js'))) {
return str_replace('https://example.com', 'https://static.example.com', $url);
}
// استفاده از CDN تخصصی برای فونتها
if (in_array($file_extension, array('woff', 'woff2', 'ttf', 'eot'))) {
return str_replace('https://example.com', 'https://fonts.example.com', $url);
}
return $url;
}
add_filter('style_loader_src', 'use_specialized_cdns');
add_filter('script_loader_src', 'use_specialized_cdns');
add_filter('wp_get_attachment_url', 'use_specialized_cdns');
افزونههای یکپارچهسازی CDN با وردپرس
برخی از بهترین افزونههای یکپارچهسازی CDN برای وردپرس عبارتند از:
- Cloudflare: افزونه رسمی Cloudflare برای وردپرس
- WP Rocket: یکپارچگی با CDNهای مختلف و تنظیمات پیشرفته
- W3 Total Cache: پشتیبانی از انواع CDNها و تنظیمات دقیق
- CDN Enabler: افزونه سبک و ساده برای یکپارچهسازی CDN
- BunnyCDN: افزونه رسمی BunnyCDN برای وردپرس
Codeنکات مهم برای انتخاب و پیکربندی CDN:
• پوشش جغرافیایی: CDN با پوشش خوب در مناطق جغرافیایی مخاطبان خود انتخاب کنید
• هزینه: برخی CDNها بر اساس ترافیک و برخی بر اساس تعداد درخواست هزینه دریافت میکنند
• قابلیتها: بهینهسازی تصویر، فشردهسازی، HTTP/3 و امنیت را در نظر بگیرید
• سهولت یکپارچهسازی: برخی CDNها یکپارچهسازی سادهتری با وردپرس دارند
• پشتیبانی از HTTPS: اطمینان حاصل کنید که CDN از SSL رایگان پشتیبانی میکند
• امکانات کش: قابلیتهای پاکسازی کش و قوانین کش را بررسی کنید
استفاده از CDN برای وردپرس یکی از موثرترین راههای بهبود Core Web Vitals است. با پیادهسازی CDN، میتوانید زمان بارگذاری را تا 60% کاهش دهید و به امتیاز 100 در Lighthouse نزدیکتر شوید. هر چه کاربران شما از مناطق جغرافیایی گستردهتری باشند، تأثیر CDN بیشتر خواهد بود.
در بخش بعدی، به بررسی بهینهسازی فونت در وردپرس خواهیم پرداخت که تأثیر قابل توجهی بر CLS و زمان بارگذاری دارد.
14. بهینهسازی فونتها در وردپرس برای کاهش زمان بارگذاری
بهینهسازی فونت در وردپرس یکی از جنبههای مهم دستیابی به امتیاز 100 در Lighthouse است. فونتها میتوانند تأثیر قابل توجهی بر Core Web Vitals داشته باشند، زیرا بارگذاری آنها میتواند باعث تأخیر در نمایش متن (FOUT یا FOIT) و جابجایی محتوا (CLS) شود. در این بخش، تکنیکهای پیشرفته برای بهینهسازی فونتها را بررسی میکنیم.
چالشهای فونتها در Core Web Vitals
فونتها میتوانند به چند روش بر Core Web Vitals تأثیر بگذارند:
- تأثیر بر LCP: بارگذاری فونتها میتواند رندر محتوای اصلی را به تأخیر بیندازد.
- تأثیر بر CLS: تغییر فونت پس از بارگذاری اولیه میتواند باعث جابجایی محتوا شود.
- تأثیر بر FID: بارگذاری فایلهای فونت بزرگ میتواند thread اصلی را مسدود کند.
تکنیکهای بارگذاری بهینه فونت
1. استفاده از font-display
CSS دارای ویژگی font-display
است که نحوه نمایش متن هنگام بارگذاری فونت را کنترل میکند:
PHPfunction optimize_font_display() {
?>
<style>
/* استفاده از font-display: swap برای جلوگیری از FOIT */
@font-face {
font-family: 'Primary Font';
src: url('<?php echo get_template_directory_uri(); ?>/assets/fonts/primary-font.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap; /* نمایش فوری متن با فونت جایگزین */
}
/* استفاده از font-display: optional برای جلوگیری از CLS */
@font-face {
font-family: 'Secondary Font';
src: url('<?php echo get_template_directory_uri(); ?>/assets/fonts/secondary-font.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: optional; /* استفاده از فونت فقط اگر از قبل کش شده باشد */
}
/* استفاده از font-display: fallback برای فونتهای تزئینی */
@font-face {
font-family: 'Decorative Font';
src: url('<?php echo get_template_directory_uri(); ?>/assets/fonts/decorative-font.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: fallback; /* زمان کوتاه انتظار، سپس استفاده از فونت جایگزین */
}
</style>
<?php
}
add_action('wp_head', 'optimize_font_display', 1);
2. پیشبارگذاری فونتهای ضروری
پیشبارگذاری فونتهای ضروری باعث بارگذاری زودتر آنها میشود:
PHPfunction preload_critical_fonts() {
?>
<!-- پیشبارگذاری فونتهای ضروری -->
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/assets/fonts/primary-font.woff2" as="font" type="font/woff2" crossorigin>
<?php
}
add_action('wp_head', 'preload_critical_fonts', 1);
3. استفاده از فونتهای متغیر (Variable Fonts)
فونتهای متغیر امکان ارائه چندین وزن و سبک در یک فایل را فراهم میکنند:
PHPfunction use_variable_fonts() {
?>
<style>
/* استفاده از فونت متغیر */
@font-face {
font-family: 'Variable Font';
src: url('<?php echo get_template_directory_uri(); ?>/assets/fonts/variable-font.woff2') format('woff2 supports variations'),
url('<?php echo get_template_directory_uri(); ?>/assets/fonts/variable-font.woff2') format('woff2-variations');
font-weight: 100 900; /* محدوده وزنها */
font-stretch: 75% 125%; /* محدوده عرض */
font-style: normal italic; /* سبکها */
font-display: swap;
}
/* استفاده از فونت متغیر با تنظیمات مختلف */
h1 {
font-family: 'Variable Font';
font-weight: 700; /* وزن سنگین */
font-stretch: 100%; /* عرض معمولی */
font-style: normal;
}
h2 {
font-family: 'Variable Font';
font-weight: 300; /* وزن سبک */
font-stretch: 110%; /* عرض بیشتر */
font-style: italic;
}
</style>
<?php
}
add_action('wp_head', 'use_variable_fonts');
4. زیرمجموعهسازی فونتها (Font Subsetting)
زیرمجموعهسازی فونتها باعث کاهش حجم فایلهای فونت میشود:
PHPfunction load_subset_fonts() {
// تشخیص زبان سایت
$site_language = get_locale();
// انتخاب زیرمجموعه مناسب
$subset = 'latin';
if ($site_language == 'fa_IR') {
$subset = 'arabic';
} elseif ($site_language == 'ru_RU') {
$subset = 'cyrillic';
}
// بارگذاری فونت گوگل با زیرمجموعه مناسب
wp_enqueue_style(
'google-fonts',
'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&family=Roboto:wght@400;700&display=swap&subset=' . $subset,
array(),
null
);
}
add_action('wp_enqueue_scripts', 'load_subset_fonts');
5. میزبانی محلی فونتهای گوگل
میزبانی محلی فونتهای گوگل باعث افزایش سرعت بارگذاری و بهبود حریم خصوصی میشود:
PHPfunction host_google_fonts_locally() {
// حذف فونتهای گوگل
wp_dequeue_style('google-fonts');
wp_dequeue_style('wf-roboto');
wp_dequeue_style('wf-open-sans');
// افزودن فونتهای میزبانی شده محلی
wp_enqueue_style('local-fonts', get_template_directory_uri() . '/assets/fonts/google-fonts.css', array(), '1.0.0');
}
add_action('wp_enqueue_scripts', 'host_google_fonts_locally', 100);
فایل google-fonts.css
باید شامل تعریف فونتهای میزبانی شده محلی باشد:
CSS/* google-fonts.css */
@font-face {
font-family: 'Roboto';
src: url('roboto-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Roboto';
src: url('roboto-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Open Sans';
src: url('open-sans-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Open Sans';
src: url('open-sans-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
6. استفاده از فونتهای سیستمی
استفاده از فونتهای سیستمی باعث حذف نیاز به دانلود فایلهای فونت میشود:
PHPfunction use_system_fonts() {
?>
<style>
/* استفاده از فونتهای سیستمی مدرن */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
/* فونت مونواسپیس برای کد */
code, pre {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
/* فونتهای سیستمی برای عناوین */
h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
</style>
<?php
}
add_action('wp_head', 'use_system_fonts');
7. فشردهسازی فونتها
فشردهسازی فونتها با استفاده از فرمت WOFF2 باعث کاهش حجم فایل میشود:
PHPfunction compress_and_convert_fonts() {
// این کد را یک بار اجرا کنید تا فونتها را فشرده و تبدیل کند
$font_dir = get_template_directory() . '/assets/fonts/';
$fonts = glob($font_dir . '*.{ttf,otf}', GLOB_BRACE);
foreach ($fonts as $font) {
$output_woff2 = str_replace(array('.ttf', '.otf'), '.woff2', $font);
// تبدیل به WOFF2 با استفاده از woff2_compress
// نیاز به نصب woff2_compress در سرور دارد
exec("woff2_compress " . escapeshellarg($font));
echo "Converted: " . basename($font) . " to " . basename($output_woff2) . "<br>";
}
}
// برای اجرای این تابع، یک بار به URL زیر مراجعه کنید
// https://your-site.com/wp-admin/tools.php?convert_fonts=1
function handle_font_conversion() {
if (current_user_can('manage_options') && isset($_GET['convert_fonts'])) {
compress_and_convert_fonts();
exit;
}
}
add_action('admin_init', 'handle_font_conversion');
جلوگیری از CLS ناشی از فونتها
1. استفاده از size-adjust
ویژگی size-adjust
به شما امکان میدهد اندازه فونتهای جایگزین را با فونت اصلی تطبیق دهید:
PHPfunction prevent_font_cls() {
?>
<style>
/* تعریف فونت جایگزین مشابه با فونت اصلی */
@font-face {
font-family: 'Roboto Fallback';
src: local('Arial');
size-adjust: 97.5%; /* تنظیم اندازه برای تطابق با Roboto */
ascent-override: 93%; /* تنظیم ارتفاع حروف */
descent-override: 25%; /* تنظیم ارتفاع زیر خط */
line-gap-override: 0%; /* تنظیم فاصله خطوط */
}
/* استفاده از فونت جایگزین تا زمان بارگذاری فونت اصلی */
body {
font-family: 'Roboto', 'Roboto Fallback', sans-serif;
}
</style>
<?php
}
add_action('wp_head', 'prevent_font_cls', 1);
2. استفاده از Font Loading API
Font Loading API به شما امکان میدهد بارگذاری فونتها را به صورت برنامهای کنترل کنید:
PHPfunction use_font_loading_api() {
?>
<script>
// استفاده از Font Loading API
if ('fonts' in document) {
// پیشبارگذاری فونتها
const fontPromises = [
document.fonts.load('1em "Roboto"'),
document.fonts.load('bold 1em "Roboto"'),
document.fonts.load('1em "Open Sans"'),
document.fonts.load('bold 1em "Open Sans"')
];
// اضافه کردن کلاس به body پس از بارگذاری فونتها
Promise.all(fontPromises)
.then(() => {
document.documentElement.classList.add('fonts-loaded');
})
.catch(err => {
console.log('Font loading failed:', err);
});
}
</script>
<style>
/* استایلهای پیشفرض با فونتهای سیستمی */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
/* استایلها پس از بارگذاری فونتها */
.fonts-loaded body {
font-family: "Roboto", sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.fonts-loaded h1,
.fonts-loaded h2,
.fonts-loaded h3,
.fonts-loaded h4,
.fonts-loaded h5,
.fonts-loaded h6 {
font-family: "Open Sans", sans-serif;
}
</style>
<?php
}
add_action('wp_head', 'use_font_loading_api', 1);
3. محدود کردن تعداد وزنهای فونت
استفاده از تعداد زیاد وزنهای فونت باعث افزایش حجم دانلود میشود:
PHPfunction limit_font_weights() {
// حذف فونتهای گوگل پیشفرض
wp_dequeue_style('google-fonts');
// افزودن فونتها با وزنهای محدود
wp_enqueue_style(
'limited-google-fonts',
'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap',
array(),
null
);
}
add_action('wp_enqueue_scripts', 'limit_font_weights', 999);
افزونههای بهینهسازی فونت
برخی از بهترین افزونههای بهینهسازی فونت در وردپرس عبارتند از:
- OMGF (Host Google Fonts Locally): میزبانی محلی فونتهای گوگل
- Autoptimize: بهینهسازی و فشردهسازی فونتها
- WP Rocket: مدیریت پیشرفته فونتها و preload
- Perfmatters: کنترل دقیق بر بارگذاری فونتها
- Asset CleanUp: مدیریت فونتها در صفحات مختلف
جدول ۱۳: مقایسه تکنیکهای بهینهسازی فونت
تکنیک | بهترین برای | پیچیدگی پیادهسازی | تأثیر بر Core Web Vitals |
---|---|---|---|
font-display: swap | تمام سایتها | کم | زیاد (CLS) |
پیشبارگذاری فونتها | فونتهای ضروری | کم | زیاد (LCP) |
فونتهای متغیر | سایتهای با چندین وزن فونت | متوسط | متوسط |
زیرمجموعهسازی | سایتهای چندزبانه | متوسط | متوسط |
میزبانی محلی فونتها | سایتهای با فونتهای گوگل | متوسط | زیاد (LCP) |
فونتهای سیستمی | سایتهای ساده | بسیار زیاد | کم |
فشردهسازی فونتها | تمام سایتها | کم | متوسط |
size-adjust | سایتهای با فونتهای سفارشی | متوسط | زیاد (CLS) |
Font Loading API | سایتهای پیچیده | زیاد | زیاد |
بهینهسازی فونت در وردپرس یکی از جنبههای مهم دستیابی به امتیاز 100 در Lighthouse است. با پیادهسازی تکنیکهای فوق، میتوانید CLS را کاهش داده و LCP را بهبود بخشید. به یاد داشته باشید که فونتها باید تعادلی بین زیبایی و عملکرد برقرار کنند، و گاهی استفاده از فونتهای سیستمی میتواند بهترین انتخاب باشد.
در بخش بعدی، به بررسی تکنیکهای Preload، Prefetch و Preconnect خواهیم پرداخت که میتوانند بارگذاری منابع را بهینه کنند.
15. پیادهسازی تکنیکهای Preload، Prefetch و Preconnect در وردپرس
تکنیکهای Preload، Prefetch و Preconnect ابزارهای قدرتمندی برای بهینهسازی بارگذاری منابع هستند که میتوانند تأثیر قابل توجهی بر Core Web Vitals داشته باشند. این تکنیکها به مرورگر اجازه میدهند منابع مهم را زودتر بارگذاری کند و اتصالات را پیش از نیاز برقرار سازد. در این بخش، نحوه پیادهسازی این تکنیکها در وردپرس را بررسی میکنیم.
درک تفاوت بین Preload، Prefetch و Preconnect
قبل از پیادهسازی، باید تفاوت بین این تکنیکها را درک کنیم:
- Preload: به مرورگر میگوید که منبع خاصی برای صفحه فعلی ضروری است و باید هر چه زودتر بارگذاری شود.
- Prefetch: به مرورگر میگوید که منبع خاصی ممکن است برای صفحه بعدی مورد نیاز باشد و در زمان بیکاری بارگذاری شود.
- Preconnect: به مرورگر میگوید که اتصال به دامنه خاصی را پیش از نیاز برقرار کند (DNS، TCP، TLS).
پیادهسازی Preload برای منابع ضروری
1. Preload برای فایلهای CSS ضروری
PHPfunction preload_critical_css() {
?>
<!-- Preload برای CSS ضروری -->
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/assets/css/critical.css" as="style">
<?php
}
add_action('wp_head', 'preload_critical_css', 1);
2. Preload برای فونتهای ضروری
PHPfunction preload_critical_fonts() {
?>
<!-- Preload برای فونتهای ضروری -->
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/assets/fonts/primary-font.woff2" as="font" type="font/woff2" crossorigin>
<?php
}
add_action('wp_head', 'preload_critical_fonts', 1);
3. Preload برای تصاویر مهم (مانند تصویر هدر)
PHPfunction preload_hero_image() {
if (is_front_page() || is_home()) {
$hero_image_url = get_template_directory_uri() . '/assets/images/hero.webp';
?>
<!-- Preload برای تصویر هدر -->
<link rel="preload" href="<?php echo $hero_image_url; ?>" as="image" type="image/webp">
<?php
}
}
add_action('wp_head', 'preload_hero_image', 1);
4. Preload برای اسکریپتهای ضروری
PHPfunction preload_critical_scripts() {
?>
<!-- Preload برای اسکریپتهای ضروری -->
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/assets/js/navigation.js" as="script">
<?php
}
add_action('wp_head', 'preload_critical_scripts', 1);
5. Preload پویا برای محتوای LCP
PHPfunction preload_lcp_element() {
global $post;
if (is_single() && has_post_thumbnail($post->ID)) {
// Preload تصویر شاخص پست
$featured_image = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID), 'large');
if ($featured_image) {
$image_url = $featured_image[0];
$image_ext = pathinfo($image_url, PATHINFO_EXTENSION);
$mime_type = 'image/jpeg'; // پیشفرض
if ($image_ext === 'png') {
$mime_type = 'image/png';
} elseif ($image_ext === 'webp') {
$mime_type = 'image/webp';
} elseif ($image_ext === 'svg') {
$mime_type = 'image/svg+xml';
}
?>
<!-- Preloa برای تصویر شاخص پست -->
<link rel="preload" href="<?php echo $image_url; ?>" as="image" type="<?php echo $mime_type; ?>">
<?php
}
} elseif (is_page() && has_post_thumbnail($post->ID)) {
// Preload تصویر شاخص صفحه
$featured_image = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID), 'large');
if ($featured_image) {
?>
<!-- Preload برای تصویر شاخص صفحه -->
<link rel="preload" href="<?php echo $featured_image[0]; ?>" as="image">
<?php
}
}
}
add_action('wp_head', 'preload_lcp_element', 1);
پیادهسازی Prefetch برای صفحات و منابع آینده
1. Prefetch برای صفحات بعدی احتمالی
PHPfunction prefetch_next_pages() {
if (is_single()) {
// Prefetch صفحه اصلی برای بازگشت کاربر
?>
<link rel="prefetch" href="<?php echo home_url(); ?>">
<?php
// Prefetch پست بعدی
$next_post = get_next_post();
if ($next_post) {
?>
<link rel="prefetch" href="<?php echo get_permalink($next_post->ID); ?>">
<?php
}
} elseif (is_archive() || is_home()) {
// Prefetch اولین پست در لیست
global $wp_query;
if (!empty($wp_query->posts)) {
$first_post = $wp_query->posts[0];
?>
<link rel="prefetch" href="<?php echo get_permalink($first_post->ID); ?>">
<?php
}
}
}
add_action('wp_head', 'prefetch_next_pages', 1);
2. Prefetch هوشمند بر اساس رفتار کاربر
PHPfunction add_intelligent_prefetching() {
?>
<script>
// Prefetch هوشمند بر اساس حرکت ماوس روی لینکها
document.addEventListener('DOMContentLoaded', function() {
// لیست صفحاتی که قبلاً prefetch شدهاند
const prefetchedUrls = new Set();
// پیدا کردن تمام لینکهای داخلی
const internalLinks = Array.from(document.querySelectorAll('a'))
.filter(link => link.href.includes(window.location.hostname));
// اضافه کردن رویداد mouseover برای prefetch
internalLinks.forEach(link => {
link.addEventListener('mouseover', function() {
const url = this.href;
// اگر قبلاً prefetch نشده است
if (!prefetchedUrls.has(url)) {
prefetchedUrls.add(url);
// ایجاد تگ link برای prefetch
const prefetchLink = document.createElement('link');
prefetchLink.rel = 'prefetch';
prefetchLink.href = url;
document.head.appendChild(prefetchLink);
}
});
});
// Prefetch صفحات بر اساس اسکرول
let prefetchOnScroll = true;
window.addEventListener('scroll', function() {
if (prefetchOnScroll && window.scrollY > document.body.scrollHeight * 0.5) {
prefetchOnScroll = false; // فقط یک بار اجرا شود
// Prefetch صفحه بعدی در پستها
const nextPageLink = document.querySelector('.nav-links .nav-next a, .pagination .next a');
if (nextPageLink && !prefetchedUrls.has(nextPageLink.href)) {
prefetchedUrls.add(nextPageLink.href);
const prefetchLink = document.createElement('link');
prefetchLink.rel = 'prefetch';
prefetchLink.href = nextPageLink.href;
document.head.appendChild(prefetchLink);
}
}
}, { passive: true });
});
</script>
<?php
}
add_action('wp_footer', 'add_intelligent_prefetching', 20);
3. Prefetch منابع برای صفحات محبوب
PHPfunction prefetch_popular_pages_resources() {
// Prefetch منابع برای صفحات پربازدید
$popular_pages = get_transient('popular_pages');
if (!$popular_pages) {
// جایگزین با روشی برای یافتن صفحات محبوب سایت
$popular_pages = array(
'contact' => array(
'resources' => array(
'css' => '/wp-content/themes/your-theme/assets/css/contact-form.css',
'js' => '/wp-content/themes/your-theme/assets/js/contact-form.js'
)
),
'shop' => array(
'resources' => array(
'css' => '/wp-content/plugins/woocommerce/assets/css/woocommerce.css',
'js' => '/wp-content/plugins/woocommerce/assets/js/frontend/shop.js'
)
)
);
set_transient('popular_pages', $popular_pages, DAY_IN_SECONDS);
}
// اگر در صفحه اصلی هستیم، منابع صفحات محبوب را prefetch کنیم
if (is_front_page() || is_home()) {
foreach ($popular_pages as $page) {
if (isset($page['resources'])) {
foreach ($page['resources'] as $type => $url) {
$as_type = ($type === 'css') ? 'style' : 'script';
?>
<link rel="prefetch" href="<?php echo $url; ?>" as="<?php echo $as_type; ?>">
<?php
}
}
}
}
}
add_action('wp_head', 'prefetch_popular_pages_resources', 1);
پیادهسازی Preconnect برای دامنههای خارجی
1. Preconnect برای دامنههای مهم شخص ثالث
PHPfunction preconnect_third_party_domains() {
?>
<!-- Preconnect برای Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Preconnect برای Google Analytics -->
<link rel="preconnect" href="https://www.google-analytics.com" crossorigin>
<!-- Preconnect برای CDN -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<?php if (class_exists('WooCommerce')) : ?>
<!-- Preconnect برای Payment Gateways -->
<link rel="preconnect" href="https://checkout.paypal.com" crossorigin>
<link rel="preconnect" href="https://js.stripe.com" crossorigin>
<?php endif; ?>
<?php
}
add_action('wp_head', 'preconnect_third_party_domains', 1);
2. Preconnect پویا بر اساس افزونههای فعال
PHPfunction dynamic_preconnect_for_plugins() {
$preconnect_domains = array();
// بررسی افزونههای فعال و اضافه کردن دامنههای مرتبط
if (is_plugin_active('jetpack/jetpack.php')) {
$preconnect_domains[] = 'https://stats.wp.com';
}
if (is_plugin_active('elementor/elementor.php')) {
$preconnect_domains[] = 'https://kit-pro.fontawesome.com';
}
if (is_plugin_active('woocommerce/woocommerce.php')) {
$preconnect_domains[] = 'https://js.stripe.com';
}
if (is_plugin_active('google-analytics-for-wordpress/googleanalytics.php')) {
$preconnect_domains[] = 'https://www.googletagmanager.com';
}
// اضافه کردن تگهای preconnect
foreach ($preconnect_domains as $domain) {
echo '<link rel="preconnect" href="' . $domain . '" crossorigin>' . "\n";
}
}
add_action('wp_head', 'dynamic_preconnect_for_plugins', 1);
ترکیب تکنیکها برای عملکرد بهینه
1. استراتژی جامع برای منابع حیاتی
PHPfunction comprehensive_resource_hints() {
// شناسایی منابع حیاتی بر اساس نوع صفحه
$critical_resources = array();
// منابع مشترک برای تمام صفحات
$critical_resources['common'] = array(
'preload' => array(
array('url' => get_template_directory_uri() . '/assets/css/critical.css', 'as' => 'style'),
array('url' => get_template_directory_uri() . '/assets/fonts/primary-font.woff2', 'as' => 'font', 'type' => 'font/woff2', 'crossorigin' => true)
),
'preconnect' => array(
array('url' => 'https://fonts.googleapis.com', 'crossorigin' => true),
array('url' => 'https://www.google-analytics.com', 'crossorigin' => true)
)
);
// منابع خاص صفحه اصلی
if (is_front_page() || is_home()) {
$critical_resources['home'] = array(
'preload' => array(
array('url' => get_template_directory_uri() . '/assets/images/hero.webp', 'as' => 'image', 'type' => 'image/webp'),
array('url' => get_template_directory_uri() . '/assets/js/slider.js', 'as' => 'script')
),
'prefetch' => array(
array('url' => get_permalink(get_option('page_for_posts'))),
array('url' => get_permalink(wc_get_page_id('shop')))
)
);
}
// منابع خاص صفحات پست
if (is_single()) {
global $post;
// Preload تصویر شاخص
if (has_post_thumbnail($post->ID)) {
$featured_image = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID), 'large');
if ($featured_image) {
$critical_resources['single'] = array(
'preload' => array(
array('url' => $featured_image[0], 'as' => 'image')
)
);
}
}
// Prefetch پست بعدی و قبلی
$next_post = get_next_post();
$prev_post = get_previous_post();
$prefetch_urls = array();
if ($next_post) {
$prefetch_urls[] = array('url' => get_permalink($next_post->ID));
}
if ($prev_post) {
$prefetch_urls[] = array('url' => get_permalink($prev_post->ID));
}
if (!empty($prefetch_urls)) {
if (!isset($critical_resources['single'])) {
$critical_resources['single'] = array();
}
$critical_resources['single']['prefetch'] = $prefetch_urls;
}
}
// منابع خاص صفحات فروشگاه
if (function_exists('is_woocommerce') && is_woocommerce()) {
$critical_resources['shop'] = array(
'preload' => array(
array('url' => get_template_directory_uri() . '/assets/css/woocommerce.css', 'as' => 'style'),
array('url' => get_template_directory_uri() . '/assets/js/shop.js', 'as' => 'script')
),
'preconnect' => array(
array('url' => 'https://js.stripe.com', 'crossorigin' => true)
)
);
}
// چاپ تگهای resource hint
foreach ($critical_resources as $page_type => $resources) {
// Preload resources
if (isset($resources['preload'])) {
foreach ($resources['preload'] as $resource) {
echo '<link rel="preload" href="' . $resource['url'] . '" as="' . $resource['as'] . '"';
if (isset($resource['type'])) {
echo ' type="' . $resource['type'] . '"';
}
if (isset($resource['crossorigin']) && $resource['crossorigin']) {
echo ' crossorigin';
}
echo '>' . "\n";
}
}
// Preconnect resources
if (isset($resources['preconnect'])) {
foreach ($resources['preconnect'] as $resource) {
echo '<link rel="preconnect" href="' . $resource['url'] . '"';
if (isset($resource['crossorigin']) && $resource['crossorigin']) {
echo ' crossorigin';
}
echo '>' . "\n";
}
}
// Prefetch resources
if (isset($resources['prefetch'])) {
foreach ($resources['prefetch'] as $resource) {
echo '<link rel="prefetch" href="' . $resource['url'] . '">';
}
}
}
}
add_action('wp_head', 'comprehensive_resource_hints', 1);
2. استفاده از تکنیکهای Preload و Prefetch به صورت هوشمند
PHPfunction smart_resource_hints() {
?>
<script>
// تشخیص سرعت اتصال کاربر
function getConnectionSpeed() {
return navigator.connection ? navigator.connection.effectiveType : '4g';
}
// تشخیص Prefers-reduced-data
function prefersReducedData() {
return window.matchMedia && window.matchMedia('(prefers-reduced-data: reduce)').matches;
}
// بارگذاری هوشمند منابع بر اساس سرعت اتصال
document.addEventListener('DOMContentLoaded', function() {
const connectionSpeed = getConnectionSpeed();
const reduceData = prefersReducedData();
// فقط در اتصالهای سریع و بدون محدودیت داده، prefetch انجام شود
if ((connectionSpeed === '4g' || connectionSpeed === '3g') && !reduceData) {
// Prefetch لینکهای مهم
const importantLinks = document.querySelectorAll('.main-navigation a, .featured-posts a');
// مجموعه URLهای prefetch شده
const prefetchedUrls = new Set();
// افزودن prefetch برای لینکهای مهم
importantLinks.forEach(link => {
const url = link.href;
// بررسی اینکه URL داخلی باشد و قبلاً prefetch نشده باشد
if (url.includes(window.location.hostname) && !prefetchedUrls.has(url)) {
prefetchedUrls.add(url);
const prefetchLink = document.createElement('link');
prefetchLink.rel = 'prefetch';
prefetchLink.href = url;
document.head.appendChild(prefetchLink);
}
});
// Prefetch هوشمند با استفاده از Intersection Observer
const observeLinks = document.querySelectorAll('.content a, .sidebar a');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const link = entry.target;
const url = link.href;
if (url.includes(window.location.hostname) && !prefetchedUrls.has(url)) {
prefetchedUrls.add(url);
const prefetchLink = document.createElement('link');
prefetchLink.rel = 'prefetch';
prefetchLink.href = url;
document.head.appendChild(prefetchLink);
}
observer.unobserve(link);
}
});
}, {
rootMargin: '200px'
});
observeLinks.forEach(link => {
observer.observe(link);
});
}
});
</script>
<?php
}
add_action('wp_footer', 'smart_resource_hints', 20);
مدیریت اولویتها و جلوگیری از Preload بیش از حد
بارگذاری بیش از حد منابع میتواند اثر معکوس داشته باشد. باید اولویتبندی دقیقی انجام دهید:
PHPfunction prioritized_resource_hints() {
// محدود کردن تعداد منابع preload به 4-5 مورد مهم
$preload_limit = 5;
$preload_count = 0;
// لیست منابع با اولویتبندی
$prioritized_resources = array(
// اولویت 1: CSS ضروری
array(
'url' => get_template_directory_uri() . '/assets/css/critical.css',
'as' => 'style',
'priority' => 1
),
// اولویت 2: فونت اصلی
array(
'url' => get_template_directory_uri() . '/assets/fonts/primary-font.woff2',
'as' => 'font',
'type' => 'font/woff2',
'crossorigin' => true,
'priority' => 2
),
// اولویت 3: تصویر هدر (فقط در صفحه اصلی)
array(
'url' => get_template_directory_uri() . '/assets/images/hero.webp',
'as' => 'image',
'type' => 'image/webp',
'priority' => 3,
'condition' => function() { return is_front_page() || is_home(); }
),
// اولویت 4: اسکریپت ناوبری
array(
'url' => get_template_directory_uri() . '/assets/js/navigation.js',
'as' => 'script',
'priority' => 4
),
// اولویت 5: فونت ثانویه
array(
'url' => get_template_directory_uri() . '/assets/fonts/secondary-font.woff2',
'as' => 'font',
'type' => 'font/woff2',
'crossorigin' => true,
'priority' => 5
),
// اولویت 6: CSS ثانویه
array(
'url' => get_template_directory_uri() . '/assets/css/secondary.css',
'as' => 'style',
'priority' => 6
)
);
// مرتبسازی بر اساس اولویت
usort($prioritized_resources, function($a, $b) {
return $a['priority'] - $b['priority'];
});
// اعمال preload با محدودیت تعداد
foreach ($prioritized_resources as $resource) {
// بررسی شرط (اگر وجود داشته باشد)
if (isset($resource['condition']) && is_callable($resource['condition']) && !call_user_func($resource['condition'])) {
continue;
}
// بررسی محدودیت تعداد
if ($preload_count >= $preload_limit) {
break;
}
echo '<link rel="preload" href="' . $resource['url'] . '" as="' . $resource['as'] . '"';
if (isset($resource['type'])) {
echo ' type="' . $resource['type'] . '"';
}
if (isset($resource['crossorigin']) && $resource['crossorigin']) {
echo ' crossorigin';
}
echo '>' . "\n";
$preload_count++;
}
}
add_action('wp_head', 'prioritized_resource_hints', 1);
جدول ۱۴: مقایسه تکنیکهای Resource Hints
تکنیک | بهترین استفاده برای | اولویت بارگذاری | تأثیر بر Core Web Vitals |
---|---|---|---|
Preload | منابع ضروری صفحه فعلی (فونت، تصویر هدر، CSS) | بالا (فوری) | افزایش LCP |
Preconnect | دامنههای شخص ثالث (CDN، Google Fonts) | متوسط | کاهش TTFB، افزایش LCP |
DNS-Prefetch | دامنههای شخص ثالث در مرورگرهای قدیمی | کم | کاهش TTFB (کم) |
Prefetch | بهبود صفحات بعدی، منابعی که کاربر حتماً بعداً بازدید میکند | پایین (در زمان بیکاری) | بهبود تجربه کاربری |
Prerender | فقط برای صفحاتی که احتمال بازدید بعدی آنها بسیار بالاست | بسیار پایین | بهبود تجربه صفحات بعدی |
پیادهسازی صحیح تکنیکهای Preload، Prefetch و Preconnect میتواند تأثیر قابل توجهی بر Core Web Vitals داشته باشد. Preload میتواند LCP را تا 20% بهبود بخشد، در حالی که Preconnect میتواند زمان برقراری اتصال به منابع خارجی را تا 300 میلیثانیه کاهش دهد. Prefetch نیز میتواند تجربه کاربری را با بارگذاری پیشبینیشده صفحات بعدی بهبود بخشد.
در بخش بعدی، به بررسی استفاده از فناوریهای مدرن وب مانند HTTP/2 و HTTP/3 خواهیم پرداخت که میتوانند سرعت بارگذاری را به طور چشمگیری افزایش دهند.
17. استفاده از فناوریهای مدرن وب مانند HTTP/2 و HTTP/3 برای وردپرس
HTTP/2 و HTTP/3 پروتکلهای مدرن انتقال وب هستند که میتوانند سرعت بارگذاری سایتهای وردپرسی را به طور قابل توجهی افزایش دهند. این پروتکلها با بهینهسازی نحوه انتقال داده بین سرور و مرورگر، میتوانند تأثیر چشمگیری بر Core Web Vitals داشته باشند. در این بخش، به بررسی نحوه فعالسازی و بهرهبرداری از این پروتکلها برای بهینهسازی وردپرس میپردازیم.
مزایای HTTP/2 و HTTP/3 برای Core Web Vitals
قبل از بررسی نحوه پیادهسازی، باید مزایای این پروتکلها را درک کنیم:
مزایای HTTP/2:
- Multiplexing: امکان ارسال چندین درخواست و دریافت چندین پاسخ به صورت همزمان در یک اتصال TCP
- Binary Protocol: پروتکل باینری به جای متنی برای کارایی بیشتر
- Header Compression: فشردهسازی هدرها برای کاهش حجم دادههای منتقل شده
- Server Push: امکان ارسال منابع توسط سرور قبل از درخواست مرورگر
- حذف محدودیت تعداد اتصالات همزمان: برخلاف HTTP/1.1 که معمولاً 6-8 اتصال همزمان مجاز بود
مزایای HTTP/3:
- QUIC بر روی UDP: استفاده از پروتکل QUIC بر پایه UDP به جای TCP برای کاهش تأخیر
- کاهش زمان هندشیک: کاهش زمان برقراری اتصال اولیه
- مقاومت در برابر قطعی شبکه: بهبود عملکرد در شبکههای ناپایدار و موبایل
- بهبود امنیت: رمزگذاری دادهها به صورت پیشفرض
- کاهش Head-of-Line Blocking: حل مشکل مسدود شدن صف درخواستها
فعالسازی HTTP/2 در سرور وردپرس
1. فعالسازی HTTP/2 در Apache
برای فعالسازی HTTP/2 در Apache، نیاز به نسخه 2.4.17 یا بالاتر دارید:
Code# اضافه کردن به فایل .htaccess یا httpd.conf
<IfModule mod_http2.c>
Protocols h2 h2c http/1.1
# تنظیمات بهینه HTTP/2
H2Direct on
H2Push on
H2PushPriority * after
H2PushPriority text/css before
H2PushPriority text/javascript before
H2PushPriority image/webp after
H2PushPriority image/jpeg after
H2PushPriority image/png after
# تنظیمات سرور push
<FilesMatch "index\.php$">
Header add Link "</wp-content/themes/your-theme/assets/css/critical.css>; rel=preload; as=style"
Header add Link "</wp-content/themes/your-theme/assets/fonts/primary-font.woff2>; rel=preload; as=font; crossorigin"
Header add Link "</wp-content/themes/your-theme/assets/js/navigation.js>; rel=preload; as=script"
</FilesMatch>
</IfModule>
2. فعالسازی HTTP/2 در Nginx
برای فعالسازی HTTP/2 در Nginx، نیاز به نسخه 1.9.5 یا بالاتر دارید:
Code# اضافه کردن به بلوک server در فایل پیکربندی nginx
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# تنظیمات SSL
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# تنظیمات بهینه HTTP/2
http2_push_preload on;
http2_max_concurrent_streams 128;
http2_idle_timeout 3m;
# تنظیمات سرور push
location = / {
http2_push /wp-content/themes/your-theme/assets/css/critical.css;
http2_push /wp-content/themes/your-theme/assets/fonts/primary-font.woff2;
http2_push /wp-content/themes/your-theme/assets/js/navigation.js;
}
# سایر تنظیمات...
}
3. بررسی فعال بودن HTTP/2
برای بررسی فعال بودن HTTP/2 در سایت خود، میتوانید از ابزارهای آنلاین مانند HTTP/2 Test استفاده کنید یا کد زیر را به functions.php اضافه کنید:
PHPfunction check_http2_status() {
if (is_admin() && current_user_can('administrator')) {
$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : '';
$http2_active = (strpos($protocol, 'HTTP/2') !== false);
echo '<div class="notice notice-info is-dismissible">';
echo '<p>HTTP/2 Status: ' . ($http2_active ? '<strong style="color:green">Active</strong>' : '<strong style="color:red">Not Active</strong>') . '</p>';
echo '</div>';
}
}
add_action('admin_notices', 'check_http2_status');
فعالسازی HTTP/3 در سرور وردپرس
HTTP/3 هنوز در مراحل اولیه پذیرش است، اما میتوانید آن را در برخی سرورها فعال کنید:
1. فعالسازی HTTP/3 در Nginx
برای فعالسازی HTTP/3 در Nginx، نیاز به نسخه Nginx با پشتیبانی از QUIC دارید:
Code# اضافه کردن به بلوک server در فایل پیکربندی nginx
server {
listen 443 ssl http2;
listen 443 quic reuseport; # پورت UDP برای HTTP/3
server_name example.com www.example.com;
# تنظیمات SSL
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# تنظیمات HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
quic_retry on;
quic_gso on;
# سایر تنظیمات...
}
2. استفاده از Cloudflare برای HTTP/3
یکی از سادهترین راههای فعالسازی HTTP/3، استفاده از Cloudflare است:
- ثبت نام در Cloudflare و انتقال نیمسرورهای دامنه خود به Cloudflare
- فعالسازی HTTP/3 در بخش Network تنظیمات Cloudflare
- اضافه کردن هدر Alt-Svc به سایت وردپرس:
PHPfunction add_http3_alt_svc_header() {
if (is_cloudflare()) {
header('Alt-Svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400');
}
}
add_action('send_headers', 'add_http3_alt_svc_header');
بهینهسازی وردپرس برای HTTP/2 و HTTP/3
پس از فعالسازی این پروتکلها، باید سایت وردپرس خود را برای استفاده بهینه از آنها تنظیم کنید:
1. بهینهسازی برای HTTP/2 Multiplexing
در HTTP/2، ترکیب فایلها دیگر ضروری نیست و در برخی موارد میتواند اثر معکوس داشته باشد:
PHPfunction optimize_for_http2() {
if (is_http2()) {
// غیرفعال کردن ترکیب فایلها در افزونههای بهینهسازی
add_filter('autoptimize_css_do_concat', '__return_false');
add_filter('autoptimize_js_do_concat', '__return_false');
// غیرفعال کردن ترکیب فایلها در WP Rocket
add_filter('rocket_minify_css_concatenate', '__return_false');
add_filter('rocket_minify_js_concatenate', '__return_false');
}
}
add_action('init', 'optimize_for_http2');
// تشخیص HTTP/2
function is_http2() {
// روش 1: بررسی هدر سرور
if (isset($_SERVER['SERVER_PROTOCOL']) && strpos($_SERVER['SERVER_PROTOCOL'], 'HTTP/2') !== false) {
return true;
}
// روش 2: بررسی Cloudflare
if (isset($_SERVER['HTTP_CF_VERSION']) && strpos($_SERVER['HTTP_CF_VERSION'], 'HTTP/2') !== false) {
return true;
}
// روش 3: بررسی نوع اتصال در PHP 7.4+
if (function_exists('curl_version') && defined('CURL_VERSION_HTTP2') && (curl_version()['features'] & CURL_VERSION_HTTP2)) {
return true;
}
return false;
}
2. استفاده از Server Push به جای Resource Hints
HTTP/2 Server Push قویتر از Resource Hints است:
PHPfunction http2_server_push() {
if (!is_admin() && is_http2()) {
// منابع ضروری برای push
$resources_to_push = array(
get_template_directory_uri() . '/assets/css/critical.css' => 'style',
get_template_directory_uri() . '/assets/fonts/primary-font.woff2' => 'font',
get_template_directory_uri() . '/assets/js/navigation.js' => 'script'
);
if (is_front_page() || is_home()) {
$resources_to_push[get_template_directory_uri() . '/assets/images/hero.webp'] = 'image';
}
// اضافه کردن هدرهای Link برای server push
foreach ($resources_to_push as $uri => $as) {
$crossorigin = ($as === 'font') ? '; crossorigin' : '';
header("Link: <{$uri}>; rel=preload; as={$as}{$crossorigin}", false);
}
}
}
add_action('send_headers', 'http2_server_push');
3. استفاده از Early Hints (HTTP 103)
Early Hints یک ویژگی پیشرفته است که میتواند بارگذاری منابع را سریعتر کند:
PHPfunction send_early_hints() {
if (function_exists('http_response_code')) {
// تنظیم کد پاسخ 103 Early Hints
http_response_code(103);
// ارسال هدرهای Link برای preload
header("Link: </wp-content/themes/your-theme/assets/css/critical.css>; rel=preload; as=style", false);
header("Link: </wp-content/themes/your-theme/assets/fonts/primary-font.woff2>; rel=preload; as=font; crossorigin", false);
// ارسال هدرهای اصلی
http_response_code(200);
}
}
// این تابع باید در مراحل اولیه اجرای PHP فراخوانی شود
// معمولاً باید در wp-config.php یا از طریق هوکهای سطح پایین استفاده شود
4. بهینهسازی برای HTTP/3
PHPfunction optimize_for_http3() {
if (is_http3()) {
// اضافه کردن هدر Alt-Svc
header('Alt-Svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400');
// بهینهسازی برای HTTP/3
add_filter('style_loader_src', 'add_http3_query_param');
add_filter('script_loader_src', 'add_http3_query_param');
}
}
add_action('send_headers', 'optimize_for_http3');
// اضافه کردن پارامتر http3 به URLها برای اطمینان از استفاده از HTTP/3
function add_http3_query_param($url) {
if (strpos($url, '?') !== false) {
return $url . '&http3=1';
} else {
return $url . '?http3=1';
}
}
// تشخیص HTTP/3
function is_http3() {
// روش 1: بررسی Cloudflare
if (isset($_SERVER['HTTP_CF_PROTOCOL_VERSION']) && $_SERVER['HTTP_CF_PROTOCOL_VERSION'] == 'HTTP/3') {
return true;
}
// روش 2: بررسی هدر Alt-Svc
if (isset($_SERVER['HTTP_ALT_SVC']) && strpos($_SERVER['HTTP_ALT_SVC'], 'h3') !== false) {
return true;
}
return false;
}
مدیریت فایلها برای HTTP/2 و HTTP/3
استراتژی مدیریت فایلها در HTTP/2 و HTTP/3 متفاوت از HTTP/1.1 است:
1. تقسیم فایلها به جای ترکیب آنها
PHPfunction split_files_for_http2() {
if (is_http2() || is_http3()) {
// اضافه کردن فایلهای CSS به صورت جداگانه
wp_enqueue_style('main-style', get_template_directory_uri() . '/assets/css/main.css');
wp_enqueue_style('header-style', get_template_directory_uri() . '/assets/css/header.css');
wp_enqueue_style('footer-style', get_template_directory_uri() . '/assets/css/footer.css');
wp_enqueue_style('responsive-style', get_template_directory_uri() . '/assets/css/responsive.css');
// اضافه کردن فایلهای JS به صورت جداگانه
wp_enqueue_script('navigation', get_template_directory_uri() . '/assets/js/navigation.js', array(), '1.0', true);
wp_enqueue_script('search', get_template_directory_uri() . '/assets/js/search.js', array(), '1.0', true);
wp_enqueue_script('slider', get_template_directory_uri() . '/assets/js/slider.js', array(), '1.0', true);
} else {
// در HTTP/1.1، از فایلهای ترکیبی استفاده کنید
wp_enqueue_style('combined-style', get_template_directory_uri() . '/assets/css/combined.css');
wp_enqueue_script('combined-script', get_template_directory_uri() . '/assets/js/combined.js', array(), '1.0', true);
}
}
add_action('wp_enqueue_scripts', 'split_files_for_http2');
2. مینیفای کردن فایلها بدون ترکیب
PHPfunction minify_without_concat() {
if (is_http2() || is_http3()) {
// تنظیمات Autoptimize
add_filter('autoptimize_css_do_minify', '__return_true');
add_filter('autoptimize_js_do_minify', '__return_true');
add_filter('autoptimize_css_do_concat', '__return_false');
add_filter('autoptimize_js_do_concat', '__return_false');
// تنظیمات WP Rocket
add_filter('rocket_minify_css', '__return_true');
add_filter('rocket_minify_js', '__return_true');
add_filter('rocket_minify_css_concatenate', '__return_false');
add_filter('rocket_minify_js_concatenate', '__return_false');
}
}
add_action('init', 'minify_without_concat');
تنظیمات سرور برای عملکرد بهینه
1. تنظیمات Apache برای HTTP/2
Code# اضافه کردن به فایل .htaccess
<IfModule mod_http2.c>
# تنظیم اندازه بهینه فریم
H2MaxSessionStreams 256
H2WindowSize 65535
# فعال کردن HTTP/2 Server Push
H2Push on
# اولویتبندی منابع برای push
H2PushPriority text/css before
H2PushPriority text/javascript before
H2PushPriority font/woff2 before
H2PushPriority image/webp after
H2PushPriority image/jpeg after
H2PushPriority image/png after
# تنظیمات بهینه برای HTTP/2
H2Direct on
H2StreamMaxMemSize 10485760
</IfModule>
2. تنظیمات Nginx برای HTTP/2 و HTTP/3
Code# اضافه کردن به فایل پیکربندی nginx
http {
# تنظیمات HTTP/2
http2_max_field_size 16k;
http2_max_header_size 32k;
http2_max_requests 1000;
http2_idle_timeout 3m;
http2_recv_timeout 30s;
http2_push_preload on;
# تنظیمات HTTP/3
quic_max_datagram_size 1452;
quic_retry on;
quic_gso on;
server {
listen 443 ssl http2;
listen 443 quic reuseport; # برای HTTP/3
# تنظیمات SSL
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# هدر Alt-Svc برای HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400, h3-29=":443"; ma=86400';
# تنظیمات سرور push
location = / {
http2_push /wp-content/themes/your-theme/assets/css/critical.css;
http2_push /wp-content/themes/your-theme/assets/fonts/primary-font.woff2;
}
# سایر تنظیمات...
}
}
نظارت و تست پروتکلهای HTTP/2 و HTTP/3
1. افزودن ابزار تشخیص به پنل مدیریت
PHPfunction add_http_protocol_info() {
if (is_admin() && current_user_can('administrator')) {
$protocol = 'HTTP/1.1';
if (is_http2()) {
$protocol = 'HTTP/2';
}
if (is_http3()) {
$protocol = 'HTTP/3';
}
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Current HTTP Protocol: <strong>' . $protocol . '</strong></p>';
// اضافه کردن لینک تست
echo '<p>Test your site: ';
echo '<a href="https://tools.keycdn.com/http2-test?url=' . urlencode(home_url()) . '" target="_blank">HTTP/2 Test</a> | ';
echo '<a href="https://http3check.net/?url=' . urlencode(home_url()) . '" target="_blank">HTTP/3 Test</a>';
echo '</p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_http_protocol_info');
2. ثبت آمار استفاده از پروتکلها
PHPfunction log_http_protocol_usage() {
// ثبت استفاده از پروتکلها در دیتابیس
$protocol = 'HTTP/1.1';
if (is_http2()) {
$protocol = 'HTTP/2';
}
if (is_http3()) {
$protocol = 'HTTP/3';
}
// دریافت آمار فعلی
$stats = get_option('http_protocol_stats', array(
'HTTP/1.1' => 0,
'HTTP/2' => 0,
'HTTP/3' => 0
));
// بروزرسانی آمار
$stats[$protocol]++;
// ذخیره آمار
update_option('http_protocol_stats', $stats);
}
add_action('wp', 'log_http_protocol_usage', 999);
// نمایش آمار در پنل مدیریت
function display_http_protocol_stats() {
if (is_admin() && current_user_can('administrator') && isset($_GET['page']) && $_GET['page'] === 'http-protocol-stats') {
$stats = get_option('http_protocol_stats', array(
'HTTP/1.1' => 0,
'HTTP/2' => 0,
'HTTP/3' => 0
));
echo '<div class="wrap">';
echo '<h1>HTTP Protocol Usage Statistics</h1>';
echo '<table class="widefat">';
echo '<thead><tr><th>Protocol</th><th>Usage Count</th><th>Percentage</th></tr></thead>';
echo '<tbody>';
$total = array_sum($stats);
foreach ($stats as $protocol => $count) {
$percentage = ($total > 0) ? round(($count / $total) * 100, 2) : 0;
echo '<tr>';
echo '<td>' . $protocol . '</td>';
echo '<td>' . number_format($count) . '</td>';
echo '<td>' . $percentage . '%</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>';
}
}
// اضافه کردن منوی آمار
function add_http_protocol_stats_menu() {
add_submenu_page(
'tools.php',
'HTTP Protocol Stats',
'HTTP Protocol Stats',
'manage_options',
'http-protocol-stats',
'display_http_protocol_stats'
);
}
add_action('admin_menu', 'add_http_protocol_stats_menu');
جدول ۱۵: مقایسه پروتکلهای HTTP
ویژگی | HTTP/1.1 | HTTP/2 | HTTP/3 |
---|---|---|---|
پایه پروتکل | TCP | TCP | UDP (QUIC) |
Multiplexing | خبر ندارد | بله (پیشرفتهتر) | بله |
Header Compression | خبر ندارد | بله (HPACK) | بله (QPACK) |
Server Push | خبر ندارد | بله | بله |
زمان هندشیک | 3-way TCP + TLS | 3-way TCP + TLS | 0-RTT با 1-RTT |
مقاومت در برابر قطعی شبکه | ضعیف | متوسط | عالی |
پشتیبانی در مرورگرها | کامل | عالی (95%) | خوب (75%) |
بهبود TTFB | مبنا | 40-60% بهبود | 60-75% بهبود |
تأثیر بر LCP | مبنا | 20-30% بهبود | 50-75% بهبود |
استفاده از HTTP/2 و HTTP/3 یکی از موثرترین راههای بهبود Core Web Vitals است. این پروتکلها میتوانند TTFB را تا 60% کاهش دهند و LCP را به طور قابل توجهی بهبود بخشند. با فعالسازی و بهینهسازی سایت وردپرس برای این پروتکلها، میتوانید به امتیاز 100 در Lighthouse نزدیکتر شوید.
در بخش بعدی، به بررسی بهینهسازی هاست و سرور برای بهبود Core Web Vitals خواهیم پرداخت، که زیربنای تمام تکنیکهای بهینهسازی است.
18. بهینهسازی هاست و سرور برای بهبود Core Web Vitals
انتخاب و بهینهسازی هاست مناسب یکی از مهمترین عوامل تأثیرگذار بر Core Web Vitals است. حتی با اجرای تمام تکنیکهای بهینهسازی دیگر، اگر هاست شما ضعیف باشد، نمیتوانید به امتیاز 100 در Lighthouse دست یابید. در این بخش، به بررسی نحوه انتخاب هاست مناسب، تنظیمات سرور و بهینهسازی TTFB (Time To First Byte) میپردازیم.
انتخاب هاست مناسب برای وردپرس
1. انواع هاست و تأثیر آنها بر Core Web Vitals
جدول ۱۶: مقایسه انواع هاستها برای وردپرس
نوع هاست | مناسب برای | مزایا | معایب | تأثیر بر Core Web Vitals |
---|---|---|---|---|
اشتراکی | سایتهای کوچک، وبلاگهای شخصی | قیمت پایین، راهاندازی آسان | منابع محدود، تأثیر همسایه بد | ضعیف تا متوسط |
VPS | سایتهای متوسط، فروشگاههای کوچک | منابع اختصاصی، قابلیت تنظیم | نیاز به دانش فنی | خوب |
سرور اختصاصی | سایتهای بزرگ، فروشگاههای پرترافیک | عملکرد بالا، کنترل کامل | قیمت بالا، مدیریت پیچیده | عالی |
هاست مدیریتشده (وردپرس) | اکثر سایتهای وردپرسی | بهینه برای وردپرس، پشتیبانی | محدودیت در مدیریت دستی | عالی |
کلود | سایتهای با ترافیک نوسانی | مقیاسپذیری، اطمینان | قیمت متغیر | متوسط تا عالی |
2. معیارهای انتخاب هاست مناسب
PHPfunction check_hosting_performance() {
if (is_admin() && current_user_can('administrator') && isset($_GET['check_hosting'])) {
// بررسی TTFB
$ttfb_start = microtime(true);
$response = wp_remote_get(home_url());
$ttfb = microtime(true) - $ttfb_start;
// بررسی PHP و MySQL
global $wpdb;
$php_version = phpversion();
$mysql_version = $wpdb->get_var("SELECT VERSION()");
// بررسی حافظه PHP
$memory_limit = ini_get('memory_limit');
// بررسی تنظیمات PHP
$max_execution_time = ini_get('max_execution_time');
$post_max_size = ini_get('post_max_size');
$upload_max_filesize = ini_get('upload_max_filesize');
// بررسی فشردهسازی GZIP
$gzip_enabled = isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false;
// بررسی HTTP/2
$http2_enabled = is_http2();
// نمایش نتایج
echo '<div class="wrap">';
echo '<h1>Hosting Performance Check</h1>';
echo '<table class="widefat">';
echo '<thead><tr><th>Metric</th><th>Value</th><th>Recommendation</th></tr></thead>';
echo '<tbody>';
// TTFB
echo '<tr>';
echo '<td>TTFB</td>';
echo '<td>' . round($ttfb * 1000, 2) . ' ms</td>';
echo '<td>' . ($ttfb < 0.2 ? '<span style="color:green">Good</span>' : ($ttfb < 0.5 ? '<span style="color:orange">Needs Improvement</span>' : '<span style="color:red">Poor</span>')) . '</td>';
echo '</tr>';
// PHP Version
echo '<tr>';
echo '<td>PHP Version</td>';
echo '<td>' . $php_version . '</td>';
echo '<td>' . (version_compare($php_version, '7.4', '>=') ? '<span style="color:green">Good</span>' : '<span style="color:red">Upgrade to PHP 7.4+</span>') . '</td>';
echo '</tr>';
// MySQL Version
echo '<tr>';
echo '<td>MySQL Version</td>';
echo '<td>' . $mysql_version . '</td>';
echo '<td>' . (version_compare($mysql_version, '5.7', '>=') ? '<span style="color:green">Good</span>' : '<span style="color:red">Upgrade to MySQL 5.7+</span>') . '</td>';
echo '</tr>';
// Memory Limit
echo '<tr>';
echo '<td>PHP Memory Limit</td>';
echo '<td>' . $memory_limit . '</td>';
echo '<td>' . (intval($memory_limit) >= 256 ? '<span style="color:green">Good</span>' : '<span style="color:red">Increase to 256M+</span>') . '</td>';
echo '</tr>';
// Max Execution Time
echo '<tr>';
echo '<td>Max Execution Time</td>';
echo '<td>' . $max_execution_time . ' seconds</td>';
echo '<td>' . ($max_execution_time >= 60 ? '<span style="color:green">Good</span>' : '<span style="color:red">Increase to 60+</span>') . '</td>';
echo '</tr>';
// GZIP
echo '<tr>';
echo '<td>GZIP Compression</td>';
echo '<td>' . ($gzip_enabled ? 'Enabled' : 'Disabled') . '</td>';
echo '<td>' . ($gzip_enabled ? '<span style="color:green">Good</span>' : '<span style="color:red">Enable GZIP</span>') . '</td>';
echo '</tr>';
// HTTP/2
echo '<tr>';
echo '<td>HTTP/2</td>';
echo '<td>' . ($http2_enabled ? 'Enabled' : 'Disabled') . '</td>';
echo '<td>' . ($http2_enabled ? '<span style="color:green">Good</span>' : '<span style="color:red">Enable HTTP/2</span>') . '</td>';
echo '</tr>';
echo '</tbody>';
echo '</table>';
echo '</div>';
exit;
}
}
add_action('admin_init', 'check_hosting_performance');
// اضافه کردن لینک به منوی ابزارها
function add_hosting_check_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Check your hosting performance for Core Web Vitals: <a href="' . admin_url('admin.php?check_hosting=1') . '" class="button button-primary">Check Now</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_hosting_check_link');
بهینهسازی سرور برای عملکرد بهتر
1. تنظیمات PHP برای عملکرد بهینه
PHP// اضافه کردن به فایل php.ini یا .user.ini
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
post_max_size = 64M
upload_max_filesize = 32M
max_input_vars = 3000
zlib.output_compression = On
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 2
opcache.save_comments = 1
اگر نمیتوانید فایل php.ini را ویرایش کنید، میتوانید برخی از این تنظیمات را در فایل .htaccess اعمال کنید:
Code# تنظیمات PHP در .htaccess
<IfModule mod_php7.c>
php_value memory_limit 256M
php_value max_execution_time 300
php_value post_max_size 64M
php_value upload_max_filesize 32M
php_value max_input_vars 3000
</IfModule>
2. تنظیمات MySQL برای عملکرد بهینه
اگر به فایل my.cnf دستر سی دارید، میتوانید این تنظیمات را اعمال کنید:
Code[mysqld]
key_buffer_size = 32M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 8 query_cache_limit = 1M query_cache_size = 16M expire_logs_days = 10 max_binlog_size = 100M innodb_buffer_pool_size = 256M innodb_flush_log_at_trx_commit = 2 innodb_file_per_table = 1 " style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgb(59 130 246 / .5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; --tw-contain-size: ; --tw-contain-layout: ; --tw-contain-paint: ; --tw-contain-style: ; box-sizing: border-box; border-width: 0px; border-style: solid; border-color: rgba(var(--gray-200),1); font-family: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-size: 12px; font-weight: inherit; line-height: inherit; letter-spacing: initial !important; color: rgba(var(--gray-700),var(--tw-text-opacity, 1)); margin: 0px; padding: 0px; text-transform: none; appearance: button; background-color: transparent; background-image: none; cursor: pointer; display: flex; align-items: center; gap: 0.25rem; transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 0.15s; --tw-text-opacity: 1;">
# تنظیمات MySQL برای وردپرس
key_buffer_size = 32M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 8 query_cache_limit = 1M query_cache_size = 16M expire_logs_days = 10 max_binlog_size = 100M innodb_buffer_pool_size = 256M innodb_flush_log_at_trx_commit = 2 innodb_file_per_table = 1
3. تنظیمات Apache برای عملکرد بهینه
Code# اضافه کردن به فایل .htaccess
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/pdf "access plus 1 month"
ExpiresByType text/html "access plus 2 days"
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
# حذف فشردهسازی برای مرورگرهای قدیمی
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# حذف فشردهسازی برای تصاویر (معمولاً از قبل فشرده هستند)
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp)$ no-gzip
</IfModule>
<IfModule mod_headers.c>
# تنظیم هدرهای کش
<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|webp|js|css|swf|woff|woff2)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
# حذف هدرهای اضافی
Header unset ETag
FileETag None
</IfModule>
# بهینهسازی Keep-Alive
<IfModule mod_headers.c>
Header set Connection keep-alive
</IfModule>
# تنظیمات مدیریت کش
<IfModule mod_headers.c>
<FilesMatch "\.(js|css|xml|gz)$">
Header append Vary: Accept-Encoding
</FilesMatch>
</IfModule>
4. تنظیمات Nginx برای عملکرد بهینه
Code# اضافه کردن به فایل پیکربندی nginx
# تنظیمات کش
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# فعالسازی GZIP
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
text/js
text/xml
text/javascript
application/javascript
application/json
application/xml
application/rss+xml
image/svg+xml;
# تنظیمات FastCGI Cache
fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
# استفاده از FastCGI Cache در بلوک server
set $skip_cache 0;
# صفحاتی که نباید کش شوند
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
set $skip_cache 1;
}
# کاربران لاگین شده را کش نکن
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 60m;
}
# تنظیمات microcaching
location / {
try_files $uri $uri/ /index.php?$args;
}
# تنظیمات بهینه
client_max_body_size 64m;
client_body_buffer_size 128k;
client_header_buffer_size 3m;
large_client_header_buffers 4 256k;
بهینهسازی TTFB (Time To First Byte)
TTFB (Time To First Byte) زمان بین درخواست مرورگر و دریافت اولین بایت پاسخ است. این معیار تأثیر مستقیمی بر LCP دارد و بهبود آن برای دستیابی به امتیاز 100 در Lighthouse ضروری است.
1. استفاده از Object Cache
PHP// بررسی وجود Object Cache
function check_object_cache_status() {
if (is_admin() && current_user_can('administrator')) {
$object_cache_enabled = wp_using_ext_object_cache();
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Object Cache Status: ' . ($object_cache_enabled ? '<span style="color:green">Enabled</span>' : '<span style="color:red">Disabled</span>') . '</p>';
if (!$object_cache_enabled) {
echo '<p>Consider installing an object cache like Redis or Memcached to improve TTFB.</p>';
}
echo '</div>';
}
}
add_action('admin_notices', 'check_object_cache_status');
برای نصب Redis Object Cache، میتوانید از افزونه Redis Object Cache استفاده کنید و فایل wp-config.php را به شکل زیر ویرایش کنید:
PHP// اضافه کردن به wp-config.php
define('WP_CACHE', true);
define('WP_REDIS_HOST', '127.0.0.1'); // یا آدرس سرور Redis
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_TIMEOUT', 1);
define('WP_REDIS_READ_TIMEOUT', 1);
define('WP_REDIS_DATABASE', 0);
2. استفاده از Page Cache
PHP// بررسی وجود Page Cache
function check_page_cache_status() {
if (is_admin() && current_user_can('administrator')) {
$page_cache_enabled = defined('WP_CACHE') && WP_CACHE;
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Page Cache Status: ' . ($page_cache_enabled ? '<span style="color:green">Enabled</span>' : '<span style="color:red">Disabled</span>') . '</p>';
if (!$page_cache_enabled) {
echo '<p>Consider enabling page caching to improve TTFB.</p>';
}
echo '</div>';
}
}
add_action('admin_notices', 'check_page_cache_status');
3. بهینهسازی کوئریهای وردپرس
PHP// بهینهسازی کوئریهای وردپرس
function optimize_wp_queries() {
// غیرفعال کردن کوئریهای اضافی
function disable_emojis() {
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('admin_print_styles', 'print_emoji_styles');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
}
add_action('init', 'disable_emojis');
// غیرفعال کردن embeds
function disable_embeds() {
remove_action('wp_head', 'wp_oembed_add_discovery_links');
remove_action('wp_head', 'wp_oembed_add_host_js');
remove_filter('pre_oembed_result', 'wp_filter_pre_oembed_result', 10);
}
add_action('init', 'disable_embeds', 9999);
// غیرفعال کردن XML-RPC
add_filter('xmlrpc_enabled', '__return_false');
// غیرفعال کردن Self Pingbacks
function disable_self_pingbacks(&$links) {
foreach ($links as $l => $link) {
if (0 === strpos($link, get_option('home'))) {
unset($links[$l]);
}
}
}
add_action('pre_ping', 'disable_self_pingbacks');
// کاهش تعداد نسخههای ذخیره شده پستها
if (!defined('WP_POST_REVISIONS')) {
define('WP_POST_REVISIONS', 3);
}
// غیرفعال کردن heartbeat API در صفحات غیرضروری
function disable_heartbeat_unless_post_edit() {
global $pagenow;
if ($pagenow != 'post.php' && $pagenow != 'post-new.php') {
wp_deregister_script('heartbeat');
}
}
add_action('init', 'disable_heartbeat_unless_post_edit', 1);
}
add_action('plugins_loaded', 'optimize_wp_queries');
4. بهینهسازی wp-config.php
PHP// اضافه کردن به wp-config.php
// غیرفعال کردن ویرایش فایل از طریق پنل مدیریت
define('DISALLOW_FILE_EDIT', true);
// غیرفعال کردن نصب افزونه و قالب
define('DISALLOW_FILE_MODS', true);
// بهینهسازی کوئریهای دیتابیس
define('SAVEQUERIES', false);
// کاهش تعداد نسخههای ذخیره شده
define('WP_POST_REVISIONS', 3);
// افزایش حافظه PHP
define('WP_MEMORY_LIMIT', '256M');
// تنظیم زمان بین autosave
define('AUTOSAVE_INTERVAL', 300); // 5 دقیقه
// تنظیم زمان empty trash
define('EMPTY_TRASH_DAYS', 7);
// فعالسازی کش
define('WP_CACHE', true);
5. بهینهسازی دیتابیس برای TTFB
PHP// بهینهسازی خودکار دیتابیس
function optimize_database_scheduled() {
global $wpdb;
// بهینهسازی جداول دیتابیس
$tables = $wpdb->get_results("SHOW TABLES LIKE '{$wpdb->prefix}%'", ARRAY_N);
if ($tables) {
foreach ($tables as $table) {
$wpdb->query("OPTIMIZE TABLE {$table[0]}");
}
}
// پاکسازی ویرایشهای قدیمی
$wpdb->query("DELETE FROM {$wpdb->posts} WHERE post_type = 'revision'");
// پاکسازی دیدگاههای اسپم و زباله
$wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_approved = 'spam' OR comment_approved = 'trash'");
// پاکسازی transientهای منقضی شده
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_%' AND option_name NOT LIKE '_transient_timeout_%'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_site_transient_%' AND option_name NOT LIKE '_site_transient_timeout_%'");
}
// زمانبندی بهینهسازی دیتابیس
if (!wp_next_scheduled('optimize_database_event')) {
wp_schedule_event(time(), 'weekly', 'optimize_database_event');
}
add_action('optimize_database_event', 'optimize_database_scheduled');
استفاده از CDN برای کاهش TTFB
PHP// بررسی استفاده از CDN
function check_cdn_usage() {
if (is_admin() && current_user_can('administrator')) {
// تلاش برای تشخیص CDN
$cdn_detected = false;
// بررسی Cloudflare
if (isset($_SERVER['HTTP_CF_RA']) || isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$cdn_detected = 'Cloudflare';
}
// بررسی هدرهای CDN دیگر
$cdn_headers = array(
'HTTP_X_CDN' => 'Generic CDN',
'HTTP_X_EDGE_LOCATION' => 'AWS CloudFront',
'HTTP_X_AKAMAI_EDGESCAPE' => 'Akamai',
'HTTP_X_FASTLY_CLIENT_IP' => 'Fastly'
);
foreach ($cdn_headers as $header => $cdn_name) {
if (isset($_SERVER[$header])) {
$cdn_detected = $cdn_name;
break;
}
}
echo '<div class="notice notice-info is-dismissible">';
echo '<p>CDN Status: ' . ($cdn_detected ? '<span style="color:green">Detected (' . $cdn_detected . ')</span>' : '<span style="color:orange">Not Detected</span>') . '</p>';
if (!$cdn_detected) {
echo '<p>Consider using a CDN to improve TTFB for users worldwide.</p>';
}
echo '</div>';
}
}
add_action('admin_notices', 'check_cdn_usage');
تست و مانیتورینگ مداوم TTFB
PHP// ثبت و مانیتورینگ TTFB
function monitor_ttfb() {
if (!is_admin()) {
$start_time = $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true);
$ttfb = (microtime(true) - $start_time) * 1000; // میلیثانیه
// ذخیره TTFB در transient
$ttfb_stats = get_transient('ttfb_stats');
if (!$ttfb_stats) {
$ttfb_stats = array(
'count' => 0,
'total' => 0,
'max' => 0,
'min' => PHP_INT_MAX,
'timestamps' => array()
);
}
$ttfb_stats['count']++;
$ttfb_stats['total'] += $ttfb;
$ttfb_stats['max'] = max($ttfb_stats['max'], $ttfb);
$ttfb_stats['min'] = min($ttfb_stats['min'], $ttfb);
// ذخیره 100 نمونه آخر
$timestamp = time();
$ttfb_stats['timestamps'][$timestamp] = $ttfb;
// حفظ فقط 100 نمونه آخر
if (count($ttfb_stats['timestamps']) > 100) {
$ttfb_stats['timestamps'] = array_slice($ttfb_stats['timestamps'], -100, 100, true);
}
set_transient('ttfb_stats', $ttfb_stats, DAY_IN_SECONDS);
// اضافه کردن هدر TTFB برای دیباگ
if (current_user_can('administrator')) {
header('X-TTFB: ' . round($ttfb, 2) . 'ms');
}
}
}
add_action('wp_footer', 'monitor_ttfb', 999);
// نمایش آمار TTFB در پنل مدیریت
function display_ttfb_stats() {
if (is_admin() && current_user_can('administrator') && isset($_GET['page']) && $_GET['page'] === 'ttfb-stats') {
$ttfb_stats = get_transient('ttfb_stats');
echo '<div class="wrap">';
echo '<h1>TTFB Statistics</h1>';
if ($ttfb_stats && $ttfb_stats['count'] > 0) {
$avg_ttfb = $ttfb_stats['total'] / $ttfb_stats['count'];
echo '<div style="margin-bottom: 20px;">';
echo '<p><strong>Average TTFB:</strong> ' . round($avg_ttfb, 2) . ' ms</p>';
echo '<p><strong>Min TTFB:</strong> ' . round($ttfb_stats['min'], 2) . ' ms</p>';
echo '<p><strong>Max TTFB:</strong> ' . round($ttfb_stats['max'], 2) . ' ms</p>';
echo '<p><strong>Sample Count:</strong> ' . $ttfb_stats['count'] . '</p>';
echo '</div>';
// نمایش نمودار
echo '<h2>Recent TTFB Measurements</h2>';
echo '<div style="width: 100%; height: 300px; overflow: hidden;">';
echo '<canvas id="ttfb-chart" width="800" height="300"></canvas>';
echo '</div>';
// دادههای نمودار
$chart_labels = array();
$chart_data = array();
foreach ($ttfb_stats['timestamps'] as $timestamp => $value) {
$chart_labels[] = date('H:i:s', $timestamp);
$chart_data[] = round($value, 2);
}
// اضافه کردن اسکریپت Chart.js
echo '<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>';
echo '<script>
document.addEventListener("DOMContentLoaded", function() {
var ctx = document.getElementById("ttfb-chart").getContext("2d");
var ttfbChart = new Chart(ctx, {
type: "line",
data: {
labels: ' . json_encode($chart_labels) . ',
datasets: [{
label: "TTFB (ms)",
data: ' . json_encode($chart_data) . ',
backgroundColor: "rgba(75, 192, 192, 0.2)",
borderColor: "rgba(75, 192, 192, 1)",
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
</script>';
} else {
echo '<p>No TTFB data available yet. Browse your site to collect data.</p>';
}
echo '</div>';
}
}
// اضافه کردن منوی آمار TTFB
function add_ttfb_stats_menu() {
add_submenu_page(
'tools.php',
'TTFB Statistics',
'TTFB Statistics',
'manage_options',
'ttfb-stats',
'display_ttfb_stats'
);
}
add_action('admin_menu', 'add_ttfb_stats_menu');
جدول ۱۷: تأثیر بهینهسازی سرور بر Core Web Vitals
تکنیک | تأثیر بر CLS | تأثیر بر FID | تأثیر بر LCP | تأثیر بر TTFB |
---|---|---|---|---|
ارتقای هاست | بسیار زیاد | بسیار زیاد | زیاد | متوسط |
Object Cache | کم | زیاد | زیاد | کم |
Page Cache | کم | زیاد | زیاد | کم |
PHP 7.4+ | کم | متوسط | متوسط | زیاد |
MySQL بهینه | کم | متوسط | زیاد | کم |
GZIP فعال | کم | کم | متوسط | متوسط |
HTTP/2 فعال | کم | زیاد | زیاد | کم |
HTTP/3 | کم | بسیار زیاد | زیاد | زیاد |
CDN | کم | زیاد | زیاد | زیاد |
FastCGI Cache | کم | زیاد | بسیار زیاد | زیاد |
بهینهسازی هاست و سرور یکی از مهمترین گامها در مسیر دستیابی به امتیاز 100 در Lighthouse است. یک هاست مناسب با تنظیمات بهینه میتواند TTFB را تا 80% کاهش دهد و تأثیر چشمگیری بر تمام معیارهای Core Web Vitals داشته باشد. سرمایهگذاری در یک هاست مناسب و بهینهسازی تنظیمات سرور، یکی از بهترین سرمایهگذاریهایی است که میتوانید برای بهینهسازی وردپرس انجام دهید.
در بخش بعدی، به بررسی روشهای پیشرفته سئوی تکنیکال وردپرس برای کسب امتیاز 100 در Lighthouse خواهیم پرداخت.
19. روش های پیشرفته سئو تکنیکال برای کسب امتیاز 100 در Lighthouse
سئوی تکنیکال وردپرس یکی از جنبههای مهم دستیابی به امتیاز 100 در Lighthouse است. گوگل به طور فزایندهای بر تجربه کاربری و عملکرد فنی سایتها تأکید میکند و Core Web Vitals بخش مهمی از الگوریتم رتبهبندی آن است. در این بخش، به بررسی تکنیکهای پیشرفته سئوی تکنیکال وردپرس میپردازیم که میتوانند به شما در دستیابی به امتیاز کامل در Lighthouse کمک کنند.
بهینهسازی ساختار HTML برای Lighthouse
1. اصلاح ساختار DOM
ساختار DOM پیچیده میتواند بر عملکرد و رندر صفحه تأثیر منفی بگذارد:
PHPfunction analyze_dom_structure() {
if (is_admin() && current_user_can('administrator') && isset($_GET['analyze_dom'])) {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// تحلیل عمق DOM
function analyzeDOMDepth() {
let maxDepth = 0;
let deepestElement = null;
function calculateDepth(element, depth) {
if (depth > maxDepth) {
maxDepth = depth;
deepestElement = element;
}
for (let i = 0; i < element.children.length; i++) {
calculateDepth(element.children[i], depth + 1);
}
}
calculateDepth(document.body, 1);
return {
maxDepth: maxDepth,
deepestElement: deepestElement
};
}
// تحلیل تعداد المانها
function countElements() {
return document.querySelectorAll('*').length;
}
// تحلیل اندازه DOM
function analyzeDOMSize() {
const elements = document.querySelectorAll('*');
const elementCounts = {};
elements.forEach(element => {
const tagName = element.tagName.toLowerCase();
elementCounts[tagName] = (elementCounts[tagName] || 0) + 1;
});
return {
totalElements: elements.length,
elementCounts: elementCounts
};
}
const domDepth = analyzeDOMDepth();
const elementCount = countElements();
const domSize = analyzeDOMSize();
// نمایش نتایج
const resultDiv = document.createElement('div');
resultDiv.style.position = 'fixed';
resultDiv.style.top = '32px';
resultDiv.style.right = '0';
resultDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
resultDiv.style.color = 'white';
resultDiv.style.padding = '20px';
resultDiv.style.zIndex = '9999';
resultDiv.style.maxHeight = '80vh';
resultDiv.style.overflowY = 'auto';
resultDiv.style.width = '400px';
let resultHTML = '<h2>DOM Analysis</h2>';
resultHTML += `<p><strong>Maximum DOM Depth:</strong> ${domDepth.maxDepth}</p>`;
resultHTML += `<p><strong>Total Elements:</strong> ${elementCount}</p>`;
// Lighthouse recommendations
const depthStatus = domDepth.maxDepth <= 32 ? '<span style="color:green">Good</span>' : '<span style="color:red">Too Deep</span>';
const sizeStatus = elementCount <= 1500 ? '<span style="color:green">Good</span>' : '<span style="color:red">Too Large</span>';
resultHTML += `<p><strong>DOM Depth Status:</strong> ${depthStatus}</p>`;
resultHTML += `<p><strong>DOM Size Status:</strong> ${sizeStatus}</p>`;
// Top elements
resultHTML += '<h3>Top Elements:</h3>';
resultHTML += '<ul>';
const sortedElements = Object.entries(domSize.elementCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
sortedElements.forEach(([element, count]) => {
resultHTML += `<li><strong>${element}:</strong> ${count}</li>`;
});
resultHTML += '</ul>';
// Deepest element path
if (domDepth.deepestElement) {
let path = '';
let currentElement = domDepth.deepestElement;
while (currentElement && currentElement !== document.body) {
const tagName = currentElement.tagName.toLowerCase();
const id = currentElement.id ? `#${currentElement.id}` : '';
const classes = currentElement.className ? `.${currentElement.className.replace(/\s+/g, '.')}` : '';
path = `${tagName}${id}${classes} > ${path}`;
currentElement = currentElement.parentElement;
}
path = `body > ${path}`;
path = path.slice(0, -3); // Remove last " > "
resultHTML += '<h3>Deepest Element Path:</h3>';
resultHTML += `<div style="word-break: break-all;">${path}</div>`;
}
resultDiv.innerHTML = resultHTML;
document.body.appendChild(resultDiv);
});
</script>
<?php
}
}
add_action('wp_footer', 'analyze_dom_structure', 999);
// اضافه کردن لینک به پنل مدیریت
function add_dom_analysis_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Analyze DOM structure for Lighthouse optimization: <a href="' . home_url('?analyze_dom=1') . '" target="_blank" class="button button-primary">Analyze DOM</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_dom_analysis_link');
2. بهینهسازی ساختار Heading
ساختار Heading مناسب برای SEO و دسترسیپذیری مهم است:
PHPfunction check_heading_structure() {
if (is_admin() && current_user_can('administrator') && isset($_GET['check_headings'])) {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// بررسی ساختار heading
function analyzeHeadings() {
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
const headingStructure = [];
const issues = [];
let h1Count = 0;
let previousLevel = 0;
headings.forEach(heading => {
const level = parseInt(heading.tagName.substring(1));
const text = heading.textContent.trim();
headingStructure.push({
level: level,
text: text
});
// بررسی تعداد H1
if (level === 1) {
h1Count++;
if (h1Count > 1) {
issues.push(`Multiple H1 tags found. Only one H1 is recommended.`);
}
}
// بررسی پرش سطح (مثلاً از H2 به H4)
if (previousLevel > 0 && level > previousLevel && level - previousLevel > 1) {
issues.push(`Heading level jump from H${previousLevel} to H${level} ("${text}")`);
}
previousLevel = level;
});
return {
headings: headingStructure,
issues: issues,
h1Count: h1Count
};
}
const headingAnalysis = analyzeHeadings();
// نمایش نتایج
const resultDiv = document.createElement('div');
resultDiv.style.position = 'fixed';
resultDiv.style.top = '32px';
resultDiv.style.right = '0';
resultDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
resultDiv.style.color = 'white';
resultDiv.style.padding = '20px';
resultDiv.style.zIndex = '9999';
resultDiv.style.maxHeight = '80vh';
resultDiv.style.overflowY = 'auto';
resultDiv.style.width = '400px';
let resultHTML = '<h2>Heading Structure Analysis</h2>';
// H1 status
const h1Status = headingAnalysis.h1Count === 1 ? '<span style="color:green">Good</span>' : '<span style="color:red">Issue</span>';
resultHTML += `<p><strong>H1 Count:</strong> ${headingAnalysis.h1Count} (${h1Status})</p>`;
// Issues
if (headingAnalysis.issues.length > 0) {
resultHTML += '<h3>Issues:</h3>';
resultHTML += '<ul>';
headingAnalysis.issues.forEach(issue => {
resultHTML += `<li style="color:orange">${issue}</li>`;
});
resultHTML += '</ul>';
} else {
resultHTML += '<p style="color:green"><strong>No heading structure issues found!</strong></p>';
}
// Heading structure
resultHTML += '<h3>Heading Structure:</h3>';
resultHTML += '<ul>';
headingAnalysis.headings.forEach(heading => {
const indent = ' '.repeat((heading.level - 1) * 4);
resultHTML += `<li>${indent}H${heading.level}: ${heading.text}</li>`;
});
resultHTML += '</ul>';
resultDiv.innerHTML = resultHTML;
document.body.appendChild(resultDiv);
});
</script>
<?php
}
}
add_action('wp_footer', 'check_heading_structure', 999);
// اضافه کردن لینک به پنل مدیریت
function add_heading_check_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Check heading structure for SEO: <a href="' . home_url('?check_headings=1') . '" target="_blank" class="button button-primary">Check Headings</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_heading_check_link');
3. بهینهسازی ساختار لینکها
لینکهای مناسب برای SEO و دسترسیپذیری مهم هستند:
PHPfunction optimize_links() {
// اضافه کردن rel="noopener" به لینکهای خارجی
function add_noopener_to_external_links($content) {
return preg_replace_callback('/<a(.*?)href="(.*?)"(.*?)>/i', function($matches) {
$link_attrs = $matches[1];
$href = $matches[2];
$trailing = $matches[3];
// اگر لینک خارجی است
if (strpos($href, get_site_url()) === false && strpos($href, 'http') === 0) {
// اگر target="_blank" دارد
if (strpos($link_attrs . $trailing, 'target="_blank"') !== false && strpos($link_attrs . $trailing, 'rel=') === false) {
return '<a' . $link_attrs . 'href="' . $href . '"' . $trailing . ' rel="noopener noreferrer">';
} elseif (strpos($link_attrs . $trailing, 'target="_blank"') !== false && strpos($link_attrs . $trailing, 'rel=') !== false && strpos($link_attrs . $trailing, 'noopener') === false) {
// اگر rel دارد اما noopener ندارد
return preg_replace('/rel=(["\'])(.*?)(["\'])/i', 'rel=$1$2 noopener noreferrer$3', '<a' . $link_attrs . 'href="' . $href . '"' . $trailing . '>');
}
}
return $matches[0];
}, $content);
}
add_filter('the_content', 'add_noopener_to_external_links');
add_filter('widget_text', 'add_noopener_to_external_links');
// بهبود متن لینکها (جلوگیری از "کلیک اینجا")
function check_generic_link_text($content) {
$generic_texts = array('click here', 'کلیک کنید', 'اینجا کلیک کنید', 'اینجا', 'here', 'read more', 'بیشتر', 'بیشتر بخوانید');
foreach ($generic_texts as $text) {
if (stripos($content, '<a href=') !== false && stripos($content, '>' . $text . '</a>') !== false) {
add_action('admin_notices', function() use ($text) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<p>SEO Warning: Generic link text "' . $text . '" found in your content. Consider using more descriptive link text.</p>';
echo '</div>';
});
break;
}
}
return $content;
}
add_filter('the_content', 'check_generic_link_text', 999);
// اضافه کردن aria-label به لینکهای بدون متن
function add_aria_label_to_empty_links($content) {
return preg_replace_callback('/<a(.*?)>(.*?)<\/a>/is', function($matches) {
$link_attrs = $matches[1];
$link_content = $matches[2];
// اگر لینک فقط شامل تصویر است و alt ندارد
if (preg_match('/<img((?!alt=).)*>/is', $link_content) || trim(strip_tags($link_content)) === '') {
// اضافه کردن aria-label
if (strpos($link_attrs, 'aria-label=') === false) {
// تلاش برای یافتن title
if (preg_match('/title=(["\'])(.*?)(["\'])/i', $link_attrs, $title_matches)) {
return '<a' . $link_attrs . ' aria-label="' . $title_matches[2] . '">' . $link_content . '</a>';
} else {
// استخراج href
preg_match('/href=(["\'])(.*?)(["\'])/i', $link_attrs, $href_matches);
if (isset($href_matches[2])) {
$url = $href_matches[2];
$label = str_replace(array('http://', 'https://', 'www.'), '', $url);
return '<a' . $link_attrs . ' aria-label="Link to ' . $label . '">' . $link_content . '</a>';
}
}
}
}
return $matches[0];
}, $content);
}
add_filter('the_content', 'add_aria_label_to_empty_links');
}
add_action('init', 'optimize_links');
بهینهسازی دسترسیپذیری (Accessibility)
دسترسیپذیری یکی از معیارهای مهم Lighthouse است:
1. بهبود کنتراست رنگها
PHPfunction check_color_contrast() {
if (is_admin() && current_user_can('administrator') && isset($_GET['check_contrast'])) {
?>
<script src="https://unpkg.com/color-contrast-checker"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// بررسی کنتراست رنگها
function checkColorContrast() {
const ccc = new ColorContrastChecker();
const elements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, a, li, span, button');
const contrastIssues = [];
elements.forEach(element => {
const style = window.getComputedStyle(element);
const foregroundColor = style.color;
const backgroundColor = getActualBackgroundColor(element);
if (foregroundColor && backgroundColor) {
const isLargeText = parseInt(style.fontSize) >= 18 || (parseInt(style.fontSize) >= 14 && style.fontWeight >= 700);
const contrastRatio = ccc.getContrastRatio(foregroundColor, backgroundColor);
const passes = isLargeText ?
ccc.isLevelAA(foregroundColor, backgroundColor, true) :
ccc.isLevelAA(foregroundColor, backgroundColor, false);
if (!passes) {
contrastIssues.push({
element: element.tagName.toLowerCase() + (element.className ? '.' + element.className.replace(/\s+/g, '.') : ''),
text: element.textContent.substring(0, 50) + (element.textContent.length > 50 ? '...' : ''),
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
contrastRatio: contrastRatio.toFixed(2),
required: isLargeText ? '3:1' : '4.5:1'
});
}
}
});
return contrastIssues;
}
// یافتن رنگ پسزمینه واقعی (با در نظر گرفتن شفافیت)
function getActualBackgroundColor(element) {
let currentElement = element;
let bgColor = window.getComputedStyle(currentElement).backgroundColor;
while (bgColor === 'rgba(0, 0, 0, 0)' || bgColor === 'transparent') {
currentElement = currentElement.parentElement;
if (!currentElement) {
return 'rgb(255, 255, 255)'; // رنگ پیشفرض سفید
}
bgColor = window.getComputedStyle(currentElement).backgroundColor;
}
return bgColor;
}
const contrastIssues = checkColorContrast();
// نمایش نتایج
const resultDiv = document.createElement('div');
resultDiv.style.position = 'fixed';
resultDiv.style.top = '32px';
resultDiv.style.right = '0';
resultDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
resultDiv.style.color = 'white';
resultDiv.style.padding = '20px';
resultDiv.style.zIndex = '9999';
resultDiv.style.maxHeight = '80vh';
resultDiv.style.overflowY = 'auto';
resultDiv.style.width = '400px';
let resultHTML = '<h2>Color Contrast Analysis</h2>';
if (contrastIssues.length > 0) {
resultHTML += `<p><strong>Issues Found:</strong> ${contrastIssues.length}</p>`;
resultHTML += '<ul>';
contrastIssues.forEach(issue => {
resultHTML += `<li>
<div><strong>Element:</strong> ${issue.element}</div>
<div><strong>Text:</strong> ${issue.text}</div>
<div><strong>Colors:</strong>
<span style="display:inline-block; width:14px; height:14px; background-color:${issue.foregroundColor}; border:1px solid white;"></span> ${issue.foregroundColor} on
<span style="display:inline-block; width:14px; height:14px; background-color:${issue.backgroundColor}; border:1px solid white;"></span> ${issue.backgroundColor}
</div>
<div><strong>Contrast Ratio:</strong> ${issue.contrastRatio} (Required: ${issue.required})</div>
</li>`;
});
resultHTML += '</ul>';
} else {
resultHTML += '<p style="color:green"><strong>No contrast issues found!</strong></p>';
}
resultDiv.innerHTML = resultHTML;
document.body.appendChild(resultDiv);
});
</script>
<?php
}
}
add_action('wp_footer', 'check_color_contrast', 999);
// اضافه کردن لینک به پنل مدیریت
function add_contrast_check_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Check color contrast for accessibility: <a href="' . home_url('?check_contrast=1') . '" target="_blank" class="button button-primary">Check Contrast</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_contrast_check_link');
2. اضافه کردن alt به تصاویر
PHPfunction ensure_image_alt_tags() {
// اضافه کردن alt به تصاویر بدون alt
function add_alt_to_images($content) {
return preg_replace_callback('/<img(.*?)>/i', function($matches) {
$img_tag = $matches[0];
// اگر alt ندارد
if (strpos($img_tag, 'alt=') === false) {
// تلاش برای یافتن title
if (preg_match('/title=(["\'])(.*?)(["\'])/i', $img_tag, $title_matches)) {
return str_replace('<img', '<img alt="' . $title_matches[2] . '"', $img_tag);
}
// تلاش برای یافتن نام فایل
if (preg_match('/src=(["\'])(.*?)(["\'])/i', $img_tag, $src_matches)) {
$filename = basename($src_matches[2]);
$filename = preg_replace('/\.(jpg|jpeg|png|gif|webp)$/i', '', $filename);
$filename = str_replace(array('-', '_'), ' ', $filename);
$filename = ucwords($filename);
return str_replace('<img', '<img alt="' . $filename . '"', $img_tag);
}
// اگر هیچ اطلاعاتی یافت نشد، alt خالی اضافه کن
return str_replace('<img', '<img alt=""', $img_tag);
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'add_alt_to_images');
add_filter('post_thumbnail_html', 'add_alt_to_images');
add_filter('widget_text', 'add_alt_to_images');
// اضافه کردن alt به تصاویر در زمان آپلود
function set_default_image_alt($post_ID) {
if (wp_attachment_is_image($post_ID) && !get_post_meta($post_ID, '_wp_attachment_image_alt', true)) {
$attachment = get_post($post_ID);
$title = $attachment->post_title;
if (empty($title)) {
$filename = basename($attachment->guid);
$title = preg_replace('/\.(jpg|jpeg|png|gif|webp)$/i', '', $filename);
$title = str_replace(array('-', '_'), ' ', $title);
$title = ucwords($title);
}
update_post_meta($post_ID, '_wp_attachment_image_alt', $title);
}
}
add_action('add_attachment', 'set_default_image_alt');
// بررسی تصاویر بدون alt در پنل مدیریت
function check_images_without_alt() {
if (is_admin() && current_user_can('administrator') && isset($_GET['page']) && $_GET['page'] === 'images-without-alt') {
// بررسی تصاویر بدون alt
$args = array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => '_wp_attachment_image_alt',
'compare' => 'NOT EXISTS'
)
)
);
$images_without_alt = new WP_Query($args);
echo '<div class="wrap">';
echo '<h1>Images Without Alt Text</h1>';
if ($images_without_alt->have_posts()) {
echo '<p>' . $images_without_alt->post_count . ' images found without alt text.</p>';
echo '<table class="widefat">';
echo '<thead><tr><th>Image</th><th>Attached to</th><th>Actions</th></tr></thead>';
echo '<tbody>';
while ($images_without_alt->have_posts()) {
$images_without_alt->the_post();
$image_id = get_the_ID();
$image_url = wp_get_attachment_url($image_id);
$thumbnail = wp_get_attachment_image($image_id, 'thumbnail');
$parent_id = wp_get_post_parent_id($image_id);
$parent_title = $parent_id ? get_the_title($parent_id) : 'None';
$parent_link = $parent_id ? get_edit_post_link($parent_id) : '#';
echo '<tr>';
echo '<td>' . $thumbnail . '</td>';
echo '<td>' . ($parent_id ? '<a href="' . $parent_link . '">' . $parent_title . '</a>' : $parent_title) . '</td>';
echo '<td><a href="' . get_edit_post_link($image_id) . '" class="button">Edit</a></td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
} else {
echo '<p>No images without alt text found. Great job!</p>';
}
wp_reset_postdata();
echo '</div>';
}
}
add_action('admin_init', 'check_images_without_alt');
// اضافه کردن صفحه به منوی مدیریت
function add_images_without_alt_page() {
add_submenu_page(
'tools.php',
'Images Without Alt',
'Images Without Alt',
'manage_options',
'images-without-alt',
'check_images_without_alt'
);
}
add_action('admin_menu', 'add_images_without_alt_page');
}
add_action('init', 'ensure_image_alt_tags');
3. بهبود دسترسیپذیری فرمها
PHPfunction improve_forms_accessibility() {
// اضافه کردن label به فیلدهای فرم بدون label
function add_labels_to_forms($content) {
// اضافه کردن label به فیلدهای input
$content = preg_replace_callback('/<input(.*?)>/i', function($matches) {
$input_tag = $matches[0];
$input_attrs = $matches[1];
// بررسی وجود id
if (preg_match('/id=(["\'])(.*?)(["\'])/i', $input_attrs, $id_matches)) {
$id = $id_matches[2];
// بررسی وجود label مرتبط
if (strpos($input_tag, '<label for="' . $id . '"') === false) {
// تلاش برای یافتن name یا placeholder
$label_text = '';
if (preg_match('/name=(["\'])(.*?)(["\'])/i', $input_attrs, $name_matches)) {
$label_text = ucfirst(str_replace(array('_', '-'), ' ', $name_matches[2]));
} elseif (preg_match('/placeholder=(["\'])(.*?)(["\'])/i', $input_attrs, $placeholder_matches)) {
$label_text = $placeholder_matches[2];
}
if (!empty($label_text)) {
return '<label for="' . $id . '">' . $label_text . '</label> ' . $input_tag;
}
}
}
return $input_tag;
}, $content);
return $content;
}
add_filter('the_content', 'add_labels_to_forms');
add_filter('widget_text', 'add_labels_to_forms');
// اضافه کردن aria-label به فیلدهای جستجو
function add_aria_to_search_form($form) {
$form = str_replace('type="search"', 'type="search" aria-label="Search"', $form);
$form = str_replace('type="submit"', 'type="submit" aria-label="Submit search"', $form);
return $form;
}
add_filter('get_search_form', 'add_aria_to_search_form');
// اضافه کردن required به فیلدهای الزامی در فرم دیدگاه
function add_required_to_comment_form_fields($fields) {
if (isset($fields['author'])) {
$fields['author'] = str_replace('<input', '<input required', $fields['author']);
}
if (isset($fields['email'])) {
$fields['email'] = str_replace('<input', '<input required', $fields['email']);
}
return $fields;
}
add_filter('comment_form_default_fields', 'add_required_to_comment_form_fields');
// اضافه کردن required به متن دیدگاه
function add_required_to_comment_textarea($comment_field) {
return str_replace('<textarea', '<textarea required', $comment_field);
}
add_filter('comment_form_field_comment', 'add_required_to_comment_textarea');
}
add_action('init', 'improve_forms_accessibility');
بهینهسازی SEO برای Lighthouse
1. بهبود متادیتا
PHPfunction optimize_meta_tags() {
// اضافه کردن متادیتا به هدر
function add_meta_tags() {
global $post;
// اضافه کردن meta description
if (is_single() || is_page()) {
$excerpt = get_the_excerpt($post->ID);
if (empty($excerpt)) {
$excerpt = wp_trim_words(strip_shortcodes(strip_tags($post->post_content)), 30, '...');
}
if (!empty($excerpt)) {
echo '<meta name="description" content="' . esc_attr($excerpt) . '" />' . "\n";
}
} elseif (is_category()) {
$category_description = category_description();
if (!empty($category_description)) {
echo '<meta name="description" content="' . esc_attr(wp_trim_words(strip_tags($category_description), 30, '...')) . '" />' . "\n";
}
} elseif (is_tag()) {
$tag_description = tag_description();
if (!empty($tag_description)) {
echo '<meta name="description" content="' . esc_attr(wp_trim_words(strip_tags($tag_description), 30, '...')) . '" />' . "\n";
}
} elseif (is_home() || is_front_page()) {
$site_description = get_bloginfo('description');
if (!empty($site_description)) {
echo '<meta name="description" content="' . esc_attr($site_description) . '" />' . "\n";
}
}
// اضافه کردن canonical
echo '<link rel="canonical" href="' . esc_url(get_permalink()) . '" />' . "\n";
// اضافه کردن Open Graph tags
if (is_single() || is_page()) {
echo '<meta property="og:type" content="article" />' . "\n";
echo '<meta property="og:title" content="' . esc_attr(get_the_title()) . '" />' . "\n";
echo '<meta property="og:url" content="' . esc_url(get_permalink()) . '" />' . "\n";
if (!empty($excerpt)) {
echo '<meta property="og:description" content="' . esc_attr($excerpt) . '" />' . "\n";
}
if (has_post_thumbnail()) {
$thumbnail_src = wp_get_attachment_image_src(get_post_thumbnail_id(), 'large');
echo '<meta property="og:image" content="' . esc_url($thumbnail_src[0]) . '" />' . "\n";
}
}
// اضافه کردن Twitter Card
echo '<meta name="twitter:card" content="summary_large_image" />' . "\n";
echo '<meta name="twitter:title" content="' . esc_attr(get_the_title()) . '" />' . "\n";
if (!empty($excerpt)) {
echo '<meta name="twitter:description" content="' . esc_attr($excerpt) . '" />' . "\n";
}
if (has_post_thumbnail()) {
$thumbnail_src = wp_get_attachment_image_src(get_post_thumbnail_id(), 'large');
echo '<meta name="twitter:image" content="' . esc_url($thumbnail_src[0]) . '" />' . "\n";
}
}
add_action('wp_head', 'add_meta_tags', 1);
// بررسی طول عنوان و توضیحات
function check_title_and_description_length($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
// بررسی طول عنوان
$title = get_the_title($post_id);
if (mb_strlen($title) < 30 || mb_strlen($title) > 60) {
add_filter('redirect_post_location', function($location) {
return ad_query_arg('title_length_warning', '1', $location);
});
}
// بررسی وجود و طول توضیحات
$excerpt = get_the_excerpt($post_id);
if (empty($excerpt)) {
$excerpt = wp_trim_words(strip_shortcodes(strip_tags(get_post_field('post_content', $post_id))), 30, '...');
}
if (mb_strlen($excerpt) < 120 || mb_strlen($excerpt) > 160) {
add_filter('redirect_post_location', function($location) {
return add_query_arg('description_length_warning', '1', $location);
});
}
}
add_action('save_post', 'check_title_and_description_length');
// نمایش هشدار طول عنوان و توضیحات
function show_title_description_warnings() {
if (isset($_GET['title_length_warning'])) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<p>SEO Warning: Your title length is not optimal. Recommended length is between 30-60 characters.</p>';
echo '</div>';
}
if (isset($_GET['description_length_warning'])) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<p>SEO Warning: Your meta description length is not optimal. Recommended length is between 120-160 characters.</p>';
echo '</div>';
}
}
add_action('admin_notices', 'show_title_description_warnings');
}
add_action('init', 'optimize_meta_tags');
2. بهینهسازی ساختار URL
PHPfunction optimize_url_structure() {
// تنظیم ساختار پیوند یکتا
function set_permalink_structure() {
global $wp_rewrite;
$wp_rewrite->set_permalink_structure('/%postname%/');
$wp_rewrite->flush_rules();
}
// اجرا در زمان فعالسازی تم
add_action('after_switch_theme', 'set_permalink_structure');
// حذف category از URL دستهها
function remove_category_base() {
add_filter('category_rewrite_rules', function($rules) {
global $wp_rewrite;
$categories = get_categories(array('hide_empty' => false));
if ($categories) {
foreach ($categories as $category) {
$category_nicename = $category->slug;
if ($category->parent == $category->cat_ID) {
$category->parent = 0;
} elseif ($category->parent != 0) {
$category_nicename = get_category_parents($category->parent, false, '/', true) . $category_nicename;
}
$rules[$category_nicename . '/?$'] = 'index.php?category_name=' . $category_nicename;
}
}
return $rules;
});
add_filter('query_vars', function($query_vars) {
$query_vars[] = 'category_redirect';
return $query_vars;
});
add_filter('request', function($query_vars) {
if (isset($query_vars['category_name']) && isset($query_vars['name'])) {
$term = get_term_by('slug', $query_vars['name'], 'category');
if ($term) {
$query_vars['category_name'] = $query_vars['name'];
unset($query_vars['name']);
}
}
return $query_vars;
});
}
remove_category_base();
// افزودن ریدایرکت برای URLهای قدیمی
function redirect_old_urls() {
// ریدایرکت از URLهای دارای category/
if (is_category() && strpos($_SERVER['REQUEST_URI'], '/category/') !== false) {
$category = get_queried_object();
$new_url = get_category_link($category->term_id);
if (wp_redirect($new_url, 301)) {
exit();
}
}
// ریدایرکت از URLهای دارای index.php
if (strpos($_SERVER['REQUEST_URI'], 'index.php') !== false) {
$new_url = str_replace('index.php', '', $_SERVER['REQUEST_URI']);
if (wp_redirect(home_url($new_url), 301)) {
exit();
}
}
}
add_action('template_redirect', 'redirect_old_urls');
}
add_action('init', 'optimize_url_structure');
تکنیکهای پیشرفته برای امتیاز 100 در Lighthouse
1. بهینهسازی خودکار تصاویر برای Web Vitals
PHPfunction auto_optimize_images_for_web_vitals() {
// تبدیل خودکار تصاویر به WebP
function convert_images_to_webp($attachment_id) {
if (!function_exists('imagewebp')) return;
// بررسی نوع فایل
if (!wp_attachment_is_image($attachment_id)) return;
$file = get_attached_file($attachment_id);
$path_parts = pathinfo($file);
$webp_file = $path_parts['dirname'] . '/' . $path_parts['filename'] . '.webp';
// تبدیل به WebP بر اساس نوع فایل
switch (strtolower($path_parts['extension'])) {
case 'jpeg':
case 'jpg':
$image = imagecreatefromjpeg($file);
break;
case 'png':
$image = imagecreatefrompng($file);
imagepalettetotruecolor($image);
imagealphablending($image, true);
imagesavealpha($image, true);
break;
default:
return;
}
// ذخیره تصویر WebP
imagewebp($image, $webp_file, 80);
imagedestroy($image);
// اضافه کردن به آرایه sizes
$meta = wp_get_attachment_metadata($attachment_id);
if (is_array($meta) && isset($meta['sizes'])) {
// تبدیل سایزهای دیگر
foreach ($meta['sizes'] as $size => $size_info) {
$size_file = $path_parts['dirname'] . '/' . $size_info['file'];
$size_path_parts = pathinfo($size_file);
$size_webp_file = $size_path_parts['dirname'] . '/' . $size_path_parts['filename'] . '.webp';
switch (strtolower($size_path_parts['extension'])) {
case 'jpeg':
case 'jpg':
$size_image = imagecreatefromjpeg($size_file);
break;
case 'png':
$size_image = imagecreatefrompng($size_file);
imagepalettetotruecolor($size_image);
imagealphablending($size_image, true);
imagesavealpha($size_image, true);
break;
default:
continue 2;
}
imagewebp($size_image, $size_webp_file, 80);
imagedestroy($size_image);
}
}
}
add_action('add_attachment', 'convert_images_to_webp');
// استفاده از تصاویر WebP در محتوا
function use_webp_images($content) {
return preg_replace_callback('/<img(.*?)src=(["\'])(.*?)\.(?:jpg|jpeg|png)(["\'])(.*?)>/i', function($matches) {
$img_tag = $matches[0];
$img_src = $matches[3] . '.' . (strpos($matches[3], '.jpg') !== false || strpos($matches[3], '.jpeg') !== false ? 'jpg' : 'png');
$webp_src = $matches[3] . '.webp';
// بررسی وجود فایل WebP
$upload_dir = wp_upload_dir();
$webp_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $webp_src);
if (file_exists($webp_path)) {
return '<picture>
<source srcset="' . $webp_src . '" type="image/webp">
<img' . $matches[1] . 'src=' . $matches[2] . $img_src . $matches[4] . $matches[5] . '>
</picture>';
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'use_webp_images');
// تنظیم ابعاد تصاویر برای جلوگیری از CLS
function set_image_dimensions($content) {
return preg_replace_callback('/<img(.*?)>/i', function($matches) {
$img_tag = $matches[0];
// بررسی وجود width و height
if (strpos($img_tag, 'width=') === false || strpos($img_tag, 'height=') === false) {
// استخراج src
if (preg_match('/src=(["\'])(.*?)(["\'])/i', $img_tag, $src_matches)) {
$src = $src_matches[2];
// تبدیل URL به مسیر فایل
$upload_dir = wp_upload_dir();
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $src);
if (file_exists($file_path)) {
// دریافت ابعاد تصویر
list($width, $height) = getimagesize($file_path);
// اضافه کردن ابعاد به تگ img
if (!strpos($img_tag, 'width=')) {
$img_tag = str_replace('<img', '<img width="' . $width . '"', $img_tag);
}
if (!strpos($img_tag, 'height=')) {
$img_tag = str_replace('<img', '<img height="' . $height . '"', $img_tag);
}
// اضافه کردن loading="lazy" برای تصاویر زیر fold
if (!strpos($img_tag, 'loading=')) {
$img_tag = str_replace('<img', '<img loading="lazy"', $img_tag);
}
// اضافه کردن decoding="async" برای بهبود عملکرد
if (!strpos($img_tag, 'decoding=')) {
$img_tag = str_replace('<img', '<img decoding="async"', $img_tag);
}
}
}
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'set_image_dimensions');
}
add_action('init', 'auto_optimize_images_for_web_vitals');
2. بهینهسازی JavaScript در وردپرس
PHPfunction optimize_javascript_for_web_vitals() {
// به تعویق انداختن اسکریپتهای غیرضروری
function defer_non_critical_js() {
if (is_admin()) return;
// لیست اسکریپتهای ضروری که نباید به تعویق بیفتند
$critical_scripts = array('jquery', 'jquery-core', 'jquery-migrate');
// به تعویق انداختن سایر اسکریپتها
function defer_js_files($tag, $handle, $src) {
global $critical_scripts;
if (in_array($handle, $critical_scripts)) {
return $tag;
}
// اضافه کردن defer به اسکریپتها
return str_replace(' src', ' defer src', $tag);
}
add_filter('script_loader_tag', 'defer_js_files', 10, 3);
}
add_action('wp_enqueue_scripts', 'defer_non_critical_js', 99);
// حذف اسکریپتهای غیرضروری
function remove_unnecessary_scripts() {
if (is_admin()) return;
// حذف اسکریپتهای غیرضروری
wp_deregister_script('wp-embed');
// حذف emoji
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
// حذف jQuery Migrate در فرانتاند
function remove_jquery_migrate($scripts) {
if (!is_admin() && isset($scripts->registered['jquery'])) {
$script = $scripts->registered['jquery'];
if ($script->deps) {
$script->deps = array_diff($script->deps, array('jquery-migrate'));
}
}
}
add_action('wp_default_scripts', 'remove_jquery_migrate');
}
add_action('wp_enqueue_scripts', 'remove_unnecessary_scripts', 99);
// بارگذاری JavaScript بر اساس نیاز
function load_js_conditionally() {
// بارگذاری شرطی اسکریپتها بر اساس نوع صفحه
if (is_single()) {
wp_enqueue_script('comment-reply');
}
if (is_page('contact')) {
wp_enqueue_script('contact-form');
}
// بارگذاری تنبل اسکریپتها با Intersection Observer
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// بارگذاری اسکریپتها با Intersection Observer
const lazyScripts = document.querySelectorAll('.lazy-script-container');
if (lazyScripts.length > 0 && 'IntersectionObserver' in window) {
const scriptObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const container = entry.target;
const scriptUrl = container.dataset.src;
if (scriptUrl) {
const script = document.createElement('script');
script.src = scriptUrl;
script.defer = true;
document.body.appendChild(script);
}
observer.unobserve(container);
}
});
}, {
rootMargin: '200px'
});
lazyScripts.forEach(container => {
scriptObserver.observe(container);
});
}
});
</script>
<?php
}
add_action('wp_footer', 'load_js_conditionally');
// اضافه کردن شورتکد برای بارگذاری تنبل اسکریپتها
function lazy_script_shortcode($atts) {
$atts = shortcode_atts(array(
'src' => '',
'id' => '',
'class' => ''
), $atts);
return '<div class="lazy-script-container" data-src="' . esc_url($atts['src']) . '" id="' . esc_attr($atts['id']) . '" class="' . esc_attr($atts['class']) . '"></div>';
}
add_shortcode('lazy_script', 'lazy_script_shortcode');
}
add_action('init', 'optimize_javascript_for_web_vitals');
3. بهینهسازی سرور و کش برای امتیاز 100
PHPfunction optimize_cache_for_perfect_score() {
// اضافه کردن هدرهای کش مناسب
function add_cache_headers() {
// تنظیم هدرهای کش برای انواع مختلف فایلها
$file = $_SERVER['REQUEST_URI'];
$ext = pathinfo($file, PATHINFO_EXTENSION);
$cache_extensions = array(
'css' => 31536000, // 1 سال
'js' => 31536000,
'jpg' => 31536000,
'jpeg' => 31536000,
'png' => 31536000,
'gif' => 31536000,
'webp' => 31536000,
'ico' => 31536000,
'svg' => 31536000,
'woff' => 31536000,
'woff2' => 31536000,
'ttf' => 31536000,
'eot' => 31536000,
'otf' => 31536000,
'html' => 86400, // 1 روز
'xml' => 86400
);
if (isset($cache_extensions[$ext])) {
$max_age = $cache_extensions[$ext];
header('Cache-Control: public, max-age=' . $max_age . ', immutable');
header('Pragma: public');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $max_age) . ' GMT');
} else {
// برای صفحات پویا
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: no-cache');
}
}
add_action('send_headers', 'add_cache_headers');
// پیادهسازی کش صفحه ساده
function simple_page_cache() {
if (is_admin() || is_user_logged_in() || is_search() || is_404() || is_feed()) {
return;
}
$cache_dir = WP_CONTENT_DIR . '/cache/page-cache';
if (!file_exists($cache_dir)) {
mkdir($cache_dir, 0755, true);
}
$cache_file = $cache_dir . '/' . md5($_SERVER['REQUEST_URI']) . '.html';
// بررسی وجود فایل کش معتبر
if (file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ساعت
readfile($cache_file);
exit;
}
// شروع ذخیرهسازی خروجی
ob_start(function($buffer) use ($cache_file) {
// ذخیره فقط اگر صفحه بدون خطا باشد
if (http_response_code() == 200) {
file_put_contents($cache_file, $buffer, LOCK_EX);
}
return $buffer;
});
}
// فعالسازی کش صفحه اگر افزونه کش نصب نشده باشد
if (!defined('WP_CACHE') || !WP_CACHE) {
add_action('template_redirect', 'simple_page_cache', 1);
}
// پاکسازی کش در زمان بروزرسانی محتوا
function clear_page_cache($post_id) {
$cache_dir = WP_CONTENT_DIR . '/cache/page-cache';
if (file_exists($cache_dir)) {
// پاکسازی کش صفحه پست
$post_url = get_permalink($post_id);
$cache_file = $cache_dir . '/' . md5(parse_url($post_url, PHP_URL_PATH)) . '.html';
if (file_exists($cache_file)) {
unlink($cache_file);
}
// پاکسازی کش صفحه اصلی
$home_cache = $cache_dir . '/' . md5('/') . '.html';
if (file_exists($home_cache)) {
unlink($home_cache);
}
}
}
add_action('save_post', 'clear_page_cache');
add_action('edit_post', 'clear_page_cache');
add_action('delete_post', 'clear_page_cache');
add_action('wp_trash_post', 'clear_page_cache');
add_action('publish_post', 'clear_page_cache');
add_action('edit_comment', 'clear_page_cache');
add_action('delete_comment', 'clear_page_cache');
add_action('wp_insert_comment', 'clear_page_cache');
add_action('wp_set_comment_status', 'clear_page_cache');
}
add_action('init', 'optimize_cache_for_perfect_score');
جدول ۱۸: چکلیست نهایی برای امتیاز ۱۰۰ در Lighthouse
دسته | معیار | اهمیت | تکنیک کلیدی |
---|---|---|---|
عملکرد | LCP | بسیار زیاد | بهینهسازی تصاویر، کش، سرور |
عملکرد | FID | زیاد | Web Workers، به تعویق انداختن JavaScript |
عملکرد | CLS | زیاد | تعیین ابعاد تصاویر، مدیریت فونتها |
عملکرد | TTFB | زیاد | بهینهسازی سرور، کش، دیتابیس |
دسترسیپذیری | کنتراست رنگها | متوسط | بررسی و اصلاح کنتراست رنگها |
دسترسیپذیری | alt تصاویر | زیاد | اطمینان از وجود alt برای تمام تصاویر |
دسترسیپذیری | ساختار منطقی | متوسط | ساختار منطقی و سلسلهمراتبی هدینگها |
امنیت | SSL/TLS | بسیار زیاد | استفاده از HTTPS |
بهترین شیوهها | JavaScript | متوسط | استفاده از async/defer |
SEO | عنوان و توضیحات مناسب | زیاد | متادیتا مناسب |
SEO | ساختار لینک | متوسط | استفاده از rel=”nofollow” برای لینکهای خاص |
SEO | URL | متوسط | URLهای تمیز و خوانا |
سئوی تکنیکال وردپرس یکی از جنبههای کلیدی دستیابی به امتیاز 100 در Lighthouse است. با پیادهسازی تکنیکهای فوق، نه تنها عملکرد سایت را بهبود میبخشید، بلکه رتبهبندی آن در موتورهای جستجو را نیز افزایش میدهید. به یاد داشته باشید که Core Web Vitals بخشی از الگوریتم رتبهبندی گوگل است و بهبود آن میتواند تأثیر مستقیمی بر دیده شدن سایت شما داشته باشد.
در بخش بعدی، به بررسی روشهای عیبیابی و رفع مشکلات رایج Core Web Vitals در وردپرس خواهیم پرداخت.
20. عیبیابی و رفع مشکلات رایج Core Web Vitals در وردپرس
حتی با اجرای تمام تکنیکهای بهینهسازی، ممکن است همچنان با مشکلاتی در Core Web Vitals مواجه شوید. شناسایی و رفع این مشکلات نیازمند رویکردی سیستماتیک و ابزارهای مناسب است. در این بخش، به بررسی روشهای عیبیابی و رفع مشکلات رایج Core Web Vitals در وردپرس نیاز میپردازیم.
ابزارهای عیبیابی Core Web Vitals
1. ابزار تشخیص و ثبت مشکلات Core Web Vitals
PHPfunction core_web_vitals_debugger() {
if (!is_admin() && (current_user_can('administrator') || isset($_GET['debug_cwv']))) {
?>
<script src="https://unpkg.com/[email protected]/dist/web-vitals.iife.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// ایجاد پنل دیباگ
const debugPanel = document.createElement('div');
debugPanel.id = 'cwv-debug-panel';
debugPanel.style.position = 'fixed';
debugPanel.style.bottom = '0';
debugPanel.style.right = '0';
debugPanel.style.width = '350px';
debugPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
debugPanel.style.color = 'white';
debugPanel.style.padding = '15px';
debugPanel.style.zIndex = '9999';
debugPanel.style.fontSize = '12px';
debugPanel.style.fontFamily = 'monospace';
debugPanel.style.borderRadius = '5px 0 0 0';
debugPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
debugPanel.style.maxHeight = '80vh';
debugPanel.style.overflowY = 'auto';
debugPanel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 14px;">Core Web Vitals Debugger</h3>
<button id="cwv-debug-close" style="background: none; border: none; color: white; cursor: pointer;">×</button>
</div>
<div id="cwv-metrics">
<div class="cwv-metric" id="lcp-metric">
<div class="cwv-metric-name">LCP: <span class="cwv-metric-value">Measuring...</span></div>
<div class="cwv-metric-bar">
<div class="cwv-metric-progress" style="width: 0%;"></div>
</div>
</div>
<div class="cwv-metric" id="fid-metric">
<div class="cwv-metric-name">FID: <span class="cwv-metric-value">Waiting for interaction...</span></div>
<div class="cwv-metric-bar">
<div class="cwv-metric-progress" style="width: 0%;"></div>
</div>
</div>
<div class="cwv-metric" id="cls-metric">
<div class="cwv-metric-name">CLS: <span class="cwv-metric-value">Measuring...</span></div>
<div class="cwv-metric-bar">
<div class="cwv-metric-progress" style="width: 0%;"></div>
</div>
</div>
<div class="cwv-metric" id="inp-metric">
<div class="cwv-metric-name">INP: <span class="cwv-metric-value">Measuring...</span></div>
<div class="cwv-metric-bar">
<div class="cwv-metric-progress" style="width: 0%;"></div>
</div>
</div>
</div>
<div id="cwv-issues" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Detected Issues:</h4>
<ul id="cwv-issues-list" style="margin: 5px 0; padding-left: 20px;"></ul>
</div>
<div id="cwv-resources" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Slow Resources:</h4>
<div id="cwv-resources-table" style="max-height: 150px; overflow-y: auto;"></div>
</div>
`;
document.body.appendChild(debugPanel);
// استایلهای اضافی
const style = document.createElement('style');
style.textContent = `
.cwv-metric {
margin-bottom: 10px;
}
.cwv-metric-bar {
height: 6px;
background-color: #444;
border-radius: 3px;
margin-top: 3px;
}
.cwv-metric-progress {
height: 100%;
border-radius: 3px;
transition: width 0.3s, background-color 0.3s;
}
.cwv-good {
background-color: #0cce6b;
}
.cwv-needs-improvement {
background-color: #ffa400;
}
.cwv-poor {
background-color: #ff4e42;
}
#cwv-issues-list li {
margin-bottom: 5px;
}
.cwv-resource-row {
display: flex;
justify-content: space-between;
padding: 3px 0;
border-bottom: 1px solid #444;
}
.cwv-resource-name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cwv-resource-time {
margin-left: 10px;
}
`;
document.head.appendChild(style);
// دکمه بستن پنل
document.getElementById('cwv-debug-close').addEventListener('click', function() {
debugPanel.style.display = 'none';
});
// اندازهگیری Core Web Vitals
const lcpElement = document.getElementById('lcp-metric');
const fidElement = document.getElementById('fid-metric');
const clsElement = document.getElementById('cls-metric');
const inpElement = document.getElementById('inp-metric');
const issuesList = document.getElementById('cwv-issues-list');
const resourcesTable = document.getElementById('cwv-resources-table');
// ثبت مشکلات
const issues = [];
function addIssue(issue) {
issues.push(issue);
const li = document.createElement('li');
li.textContent = issue;
issuesList.appendChild(li);
}
// بررسی منابع کند
const resourceTiming = window.performance.getEntriesByType('resource');
const slowResources = resourceTiming
.filter(entry => entry.duration > 200)
.sort((a, b) => b.duration - a.duration)
.slice(0, 5);
slowResources.forEach(resource => {
const row = document.createElement('div');
row.className = 'cwv-resource-row';
const name = document.createElement('div');
name.className = 'cwv-resource-name';
name.title = resource.name;
name.textContent = resource.name.split('/').pop() || resource.name;
const time = document.createElement('div');
time.className = 'cwv-resource-time';
time.textContent = Math.round(resource.duration) + 'ms';
row.appendChild(name);
row.appendChild(time);
resourcesTable.appendChild(row);
if (resource.duration > 500) {
addIssue(`Slow resource: ${resource.name.split('/').pop() || resource.name} (${Math.round(resource.duration)}ms)`);
}
});
// اندازهگیری LCP
webVitals.onLCP(report => {
const value = report.value;
const lcpValueElement = lcpElement.querySelector('.cwv-metric-value');
const lcpProgressElement = lcpElement.querySelector('.cwv-metric-progress');
lcpValueElement.textContent = Math.round(value) + 'ms';
let status, percentage;
if (value <= 2500) {
status = 'cwv-good';
percentage = Math.min((value / 2500) * 100, 100);
} else if (value <= 4000) {
status = 'cwv-needs-improvement';
percentage = Math.min(((value - 2500) / 1500) * 100 + 100, 200);
} else {
status = 'cwv-poor';
percentage = 100;
addIssue(`High LCP: ${Math.round(value)}ms. Check server response time and render-blocking resources.`);
}
lcpProgressElement.style.width = Math.min(percentage, 100) + '%';
lcpProgressElement.className = 'cwv-metric-progress ' + status;
// LCP element
if (report.entries && report.entries.length > 0) {
const lcpEntry = report.entries[report.entries.length - 1];
if (lcpEntry.element) {
const elementPath = getElementPath(lcpEntry.element);
// Highlight LCP element
lcpEntry.element.style.outline = '2px solid #0cce6b';
lcpEntry.element.setAttribute('title', 'LCP Element: ' + Math.round(value) + 'ms');
// Add LCP element info
const lcpInfo = document.createElement('div');
lcpInfo.style.marginTop = '5px';
lcpInfo.style.fontSize = '11px';
lcpInfo.innerHTML = `<strong>LCP Element:</strong> ${elementPath}`;
lcpElement.appendChild(lcpInfo);
}
}
});
// اندازهگیری FID
webVitals.onFID(report => {
const value = report.value;
const fidValueElement = fidElement.querySelector('.cwv-metric-value');
const fidProgressElement = fidElement.querySelector('.cwv-metric-progress');
fidValueElement.textContent = Math.round(value) + 'ms';
let status, percentage;
if (value <= 100) {
status = 'cwv-good';
percentage = Math.min((value / 100) * 100, 100);
} else if (value <= 300) {
status = 'cwv-needs-improvement';
percentage = Math.min(((value - 100) / 200) * 100 + 100, 200);
} else {
status = 'cwv-poor';
percentage = 100;
addIssue(`High FID: ${Math.round(value)}ms. Check for long-running JavaScript.`);
}
fidProgressElement.style.width = Math.min(percentage, 100) + '%';
fidProgressElement.className = 'cwv-metric-progress ' + status;
});
// اندازهگیری CLS
webVitals.onCLS(report => {
const value = report.value;
const clsValueElement = clsElement.querySelector('.cwv-metric-value');
const clsProgressElement = clsElement.querySelector('.cwv-metric-progress');
clsValueElement.textContent = value.toFixed(3);
let status, percentage;
if (value <= 0.1) {
status = 'cwv-good';
percentage = Math.min((value / 0.1) * 100, 100);
} else if (value <= 0.25) {
status = 'cwv-needs-improvement';
percentage = Math.min(((value - 0.1) / 0.15) * 100 + 100, 200);
} else {
status = 'cwv-poor';
percentage = 100;
addIssue(`High CLS: ${value.toFixed(3)}. Check for elements shifting during page load.`);
}
clsProgressElement.style.width = Math.min(percentage, 100) + '%';
clsProgressElement.className = 'cwv-metric-progress ' + status;
// CLS elements
if (report.entries && report.entries.length > 0) {
// Find the largest shift
let largestShift = report.entries[0];
report.entries.forEach(entry => {
if (entry.value > largestShift.value) {
largestShift = entry;
}
});
// Add CLS info
if (largestShift.sources && largestShift.sources.length > 0) {
const clsInfo = document.createElement('div');
clsInfo.style.marginTop = '5px';
clsInfo.style.fontSize = '11px';
const sourceElement = largestShift.sources[0].node;
if (sourceElement) {
const elementPath = getElementPath(sourceElement);
clsInfo.innerHTML = `<strong>Largest Shift Element:</strong> ${elementPath}`;
// Highlight element
sourceElement.style.outline = '2px solid #ff4e42';
sourceElement.setAttribute('title', 'Layout Shift Element: ' + largestShift.value.toFixed(3));
}
clsElement.appendChild(clsInfo);
}
}
});
// اندازهگیری INP (Interaction to Next Paint)
webVitals.onINP(report => {
const value = report.value;
const inpValueElement = inpElement.querySelector('.cwv-metric-value');
const inpProgressElement = inpElement.querySelector('.cwv-metric-progress');
inpValueElement.textContent = Math.round(value) + 'ms';
let status, percentage;
if (value <= 200) {
status = 'cwv-good';
percentage = Math.min((value / 200) * 100, 100);
} else if (value <= 500) {
status = 'cwv-needs-improvement';
percentage = Math.min(((value - 200) / 300) * 100 + 100, 200);
} else {
status = 'cwv-poor';
percentage = 100;
addIssue(`High INP: ${Math.round(value)}ms. Check for slow event handlers.`);
}
inpProgressElement.style.width = Math.min(percentage, 100) + '%';
inpProgressElement.className = 'cwv-metric-progress ' + status;
});
// Helper function to get element path
function getElementPath(element) {
if (!element) return 'Unknown';
let path = element.tagName.toLowerCase();
if (element.id) {
path += '#' + element.id;
} else if (element.className && typeof element.className === 'string') {
path += '.' + element.className.trim().replace(/\s+/g, '.');
}
return path;
}
// بررسی مشکلات عمومی
// 1. بررسی رندر-بلاکینگ CSS/JS
const renderBlockingResources = performance.getEntriesByType('resource')
.filter(resource => {
return (resource.initiatorType === 'link' || resource.initiatorType === 'script') &&
resource.responseEnd < performance.getEntriesByType('navigation')[0].domContentLoadedEventStart &&
resource.duration > 100;
});
if (renderBlockingResources.length > 0) {
addIssue(`Found ${renderBlockingResources.length} render-blocking resources. Consider using defer/async for scripts and preload for critical CSS.`);
}
// 2. بررسی تصاویر بدون ابعاد
const imagesWithoutDimensions = Array.from(document.querySelectorAll('img')).filter(img => {
return !img.hasAttribute('width') || !img.hasAttribute('height');
});
if (imagesWithoutDimensions.length > 0) {
addIssue(`Found ${imagesWithoutDimensions.length} images without width/height attributes. This can cause layout shifts.`);
// هایلایت تصاویر مشکلدار
imagesWithoutDimensions.forEach(img => {
img.style.outline = '2px solid #ff4e42';
img.setAttribute('title', 'Image without dimensions');
});
}
// 3. بررسی فونتهای بدون font-display
const fontWithoutFontDisplay = performance.getEntriesByType('resource')
.filter(resource => {
return resource.name.match(/\.(woff2?|ttf|otf|eot)$/i) && resource.duration > 100;
});
if (fontWithoutFontDisplay.length > 0) {
addIssue(`Found ${fontWithoutFontDisplay.length} potentially unoptimized fonts. Use font-display: swap.`);
}
// 4. بررسی JavaScript سنگین
setTimeout(() => {
const longTasks = performance.getEntriesByType('longtask') || [];
if (longTasks.length > 0) {
addIssue(`Found ${longTasks.length} long tasks. The longest took ${Math.round(longTasks.reduce((max, task) => Math.max(max, task.duration), 0))}ms.`);
}
}, 3000);
// 5. بررسی نشت حافظه
let lastMemory = window.performance.memory ? window.performance.memory.usedJSHeapSize : 0;
let memoryIncreases = 0;
const memoryCheckInterval = setInterval(() => {
if (window.performance.memory) {
const currentMemory = window.performance.memory.usedJSHeapSize;
if (currentMemory > lastMemory * 1.2) {
memoryIncreases++;
if (memoryIncreases >= 3) {
addIssue(`Possible memory leak detected. JS memory usage increased significantly.`);
clearInterval(memoryCheckInterval);
}
}
lastMemory = currentMemory;
}
}, 5000);
});
</script>
<?php
}
}
add_action('wp_footer', 'core_web_vitals_debugger', 999);
// اضافه کردن لینک به پنل مدیریت
function add_cwv_debugger_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Debug Core Web Vitals issues: <a href="' . home_url('?debug_cwv=1') . '" target="_blank" class="button button-primary">Launch Debugger</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_cwv_debugger_link');
2. ابزار عیبیابی تصاویر
PHPfunction image_optimization_debugger() {
if (!is_admin() && (current_user_can('administrator') || isset($_GET['debug_images']))) {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// ایجاد پنل دیباگ
const debugPanel = document.createElement('div');
debugPanel.id = 'image-debug-panel';
debugPanel.style.position = 'fixed';
debugPanel.style.top = '32px';
debugPanel.style.right = '0';
debugPanel.style.width = '350px';
debugPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
debugPanel.style.color = 'white';
debugPanel.style.padding = '15px';
debugPanel.style.zIndex = '9999';
debugPanel.style.fontSize = '12px';
debugPanel.style.fontFamily = 'monospace';
debugPanel.style.borderRadius = '0 0 0 5px';
debugPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
debugPanel.style.maxHeight = '80vh';
debugPanel.style.overflowY = 'auto';
debugPanel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 14px;">Image Optimization Debugger</h3>
<button id="image-debug-close" style="background: none; border: none; color: white; cursor: pointer;">×</button>
</div>
<div id="image-stats">
<div id="image-summary"></div>
<div id="image-issues" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Issues:</h4>
<ul id="image-issues-list" style="margin: 5px 0; padding-left: 20px;"></ul>
</div>
</div>
<div id="image-table-container" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">All Images:</h4>
<div id="image-table" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<div style="margin-top: 15px; text-align: center;">
<button id="highlight-images" style="background: #0073aa; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;">Highlight Images</button>
<button id="check-webp" style="background: #0073aa; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px; margin-left: 5px;">Check WebP Support</button>
</div>
`;
document.body.appendChild(debugPanel);
// دکمه بستن پنل
document.getElementById('image-debug-close').addEventListener('click', function() {
debugPanel.style.display = 'none';
});
// بررسی تصاویر
const images = Array.from(document.querySelectorAll('img'));
const imageIssuesList = document.getElementById('image-issues-list');
const imageSummary = document.getElementById('image-summary');
const imageTable = document.getElementById('image-table');
// آمار تصاویر
const totalSize = images.reduce((total, img) => {
// تخمین اندازه تصویر بر اساس ابعاد
const width = img.naturalWidth || img.width || 0;
const height = img.naturalHeight || img.height || 0;
const estimatedSize = (width * height * 4) / 1024; // تخمین اندازه در KB (4 بایت برای هر پیکسل)
return total + estimatedSize;
}, 0);
const imagesWithoutDimensions = images.filter(img => !img.hasAttribute('width') || !img.hasAttribute('height'));
const imagesWithoutAlt = images.filter(img => !img.hasAttribute('alt'));
const largeImages = images.filter(img => {
const width = img.naturalWidth || img.width || 0;
const height = img.naturalHeight || img.height || 0;
return width > 1000 || height > 1000;
});
const unoptimizedFormats = images.filter(img => {
const src = img.src.toLowerCase();
return src.endsWith('.png') || src.endsWith('.jpg') || src.endsWith('.jpeg') || src.endsWith('.gif');
});
// نمایش آمار
imageSummary.innerHTML = `
<div><strong>Total Images:</strong> ${images.length}</div>
<div><strong>Estimated Total Size:</strong> ${Math.round(totalSize / 1024)} MB</div>
<div><strong>Images without dimensions:</strong> ${imagesWithoutDimensions.length}</div>
<div><strong>Images without alt text:</strong> ${imagesWithoutAlt.length}</div>
<div><strong>Large images (>1000px):</strong> ${largeImages.length}</div>
<div><strong>Non-WebP images:</strong> ${unoptimizedFormats.length}</div>
`;
// ثبت مشکلات
if (imagesWithoutDimensions.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">${imagesWithoutDimensions.length} images without width/height attributes.</span> This can cause layout shifts (CLS).`;
imageIssuesList.appendChild(li);
}
if (imagesWithoutAlt.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">${imagesWithoutAlt.length} images without alt text.</span> This is bad for accessibility and SEO.`;
imageIssuesList.appendChild(li);
}
if (largeImages.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">${largeImages.length} large images.</span> Consider resizing or using responsive images.`;
imageIssuesList.appendChild(li);
}
if (unoptimizedFormats.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">${unoptimizedFormats.length} non-WebP images.</span> Convert to WebP for better compression.`;
imageIssuesList.appendChild(li);
}
// تابع برای دریافت اندازه فایل
function getFileSize(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const size = xhr.getResponseHeader('Content-Length');
callback(size ? parseInt(size) : 'Unknown');
} else {
callback('Error');
}
}
};
xhr.send(null);
}
// جدول تصاویر
images.forEach((img, index) => {
const row = document.createElement('div');
row.className = 'image-row';
row.style.display = 'flex';
row.style.borderBottom = '1px solid #444';
row.style.padding = '5px 0';
const width = img.naturalWidth || img.width || 'Unknown';
const height = img.naturalHeight || img.height || 'Unknown';
const src = img.src;
const alt = img.alt || 'Missing';
const hasDimensions = img.hasAttribute('width') && img.hasAttribute('height');
const format = src.split('.').pop().toLowerCase();
row.innerHTML = `
<div style="flex: 0 0 30px; text-align: center;">${index + 1}</div>
<div style="flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${src}">${src.split('/').pop()}</div>
<div style="flex: 0 0 80px; text-align: right;">${width}×${height}</div>
<div style="flex: 0 0 60px; text-align: center; color: ${hasDimensions ? '#0cce6b' : '#ff4e42'};">${hasDimensions ? 'Yes' : 'No'}</div>
<div style="flex: 0 0 50px; text-align: center;">${format}</div>
`;
// اضافه کردن کلاس برای هایلایت
row.dataset.imageIndex = index;
imageTable.appendChild(row);
// دریافت اندازه فایل
getFileSize(src, function(size) {
const sizeCell = document.createElement('div');
sizeCell.style.flex = '0 0 70px';
sizeCell.style.textAlign = 'right';
if (size !== 'Error' && size !== 'Unknown') {
const sizeKB = Math.round(size / 1024);
sizeCell.textContent = sizeKB + ' KB';
// هشدار برای تصاویر بزرگ
if (sizeKB > 200) {
sizeCell.style.color = '#ff4e42';
}
} else {
sizeCell.textContent = size;
}
row.appendChild(sizeCell);
});
});
// اضافه کردن هدر جدول
const tableHeader = document.createElement('div');
tableHeader.style.display = 'flex';
tableHeader.style.borderBottom = '2px solid #666';
tableHeader.style.fontWeight = 'bold';
tableHeader.style.padding = '5px 0';
tableHeader.innerHTML = `
<div style="flex: 0 0 30px; text-align: center;">#</div>
<div style="flex: 1;">Filename</div>
<div style="flex: 0 0 80px; text-align: right;">Size</div>
<div style="flex: 0 0 60px; text-align: center;">Dim.</div>
<div style="flex: 0 0 50px; text-align: center;">Format</div>
<div style="flex: 0 0 70px; text-align: right;">File size</div>
`;
imageTable.insertBefore(tableHeader, imageTable.firstChild);
// هایلایت تصاویر
document.getElementById('highlight-images').addEventListener('click', function() {
const isHighlighted = this.dataset.highlighted === 'true';
if (isHighlighted) {
// حذف هایلایت
images.forEach(img => {
img.style.outline = '';
img.title = img.dataset.originalTitle || '';
});
this.textContent = 'Highlight Images';
this.dataset.highlighted = 'false';
} else {
// اضافه کردن هایلایت
images.forEach((img, index) => {
// ذخیره title اصلی
img.dataset.originalTitle = img.title || '';
const width = img.naturalWidth || img.width || 'Unknown';
const height = img.naturalHeight || img.height || 'Unknown';
const hasDimensions = img.hasAttribute('width') && img.hasAttribute('height');
const hasAlt = img.hasAttribute('alt');
let outlineColor = '#0cce6b'; // سبز برای تصاویر بهینه
if (!hasDimensions || !hasAlt) {
outlineColor = '#ff4e42'; // قرمز برای تصاویر مشکلدار
} else if (!img.src.toLowerCase().endsWith('.webp')) {
outlineColor = '#ffa400'; // نارنجی برای تصاویر غیر WebP
}
img.style.outline = `2px solid ${outlineColor}`;
img.title = `Image #${index + 1}: ${width}×${height}, Alt: ${hasAlt ? 'Yes' : 'No'}, Dimensions: ${hasDimensions ? 'Yes' : 'No'}`;
});
this.textContent = 'Remove Highlight';
this.dataset.highlighted = 'true';
}
});
// بررسی پشتیبانی WebP
document.getElementById('check-webp').addEventListener('click', function() {
const testWebP = new Promise((resolve) => {
const webP = new Image();
webP.onload = webP.onerror = function() {
resolve(webP.height === 2);
};
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
});
testWebP.then((hasWebP) => {
alert(hasWebP ?
'Your browser supports WebP images. Great!' :
'Your browser does NOT support WebP images. Consider using fallback images for older browsers.');
});
});
});
</script>
<?php
}
}
add_action('wp_footer', 'image_optimization_debugger', 999);
// اضافه کردن لینک به پنل مدیریت
function add_image_debugger_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Debug image optimization issues: <a href="' . home_url('?debug_images=1') . '" target="_blank" class="button button-primary">Launch Image Debugger</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_image_debugger_link');
3. ابزار عیبیابی JavaScript
PHPfunction javascript_optimization_debugger() {
if (!is_admin() && (current_user_can('administrator') || isset($_GET['debug_js']))) {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// ایجاد پنل دیباگ
const debugPanel = document.createElement('div');
debugPanel.id = 'js-debug-panel';
debugPanel.style.position = 'fixed';
debugPanel.style.bottom = '0';
debugPanel.style.left = '0';
debugPanel.style.width = '350px';
debugPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
debugPanel.style.color = 'white';
debugPanel.style.padding = '15px';
debugPanel.style.zIndex = '9999';
debugPanel.style.fontSize = '12px';
debugPanel.style.fontFamily = 'monospace';
debugPanel.style.borderRadius = '0 5px 0 0';
debugPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
debugPanel.style.maxHeight = '80vh';
debugPanel.style.overflowY = 'auto';
debugPanel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 14px;">JavaScript Optimization Debugger</h3>
<button id="js-debug-close" style="background: none; border: none; color: white; cursor: pointer;">×</button>
</div>
<div id="js-stats">
<div id="js-summary"></div>
<div id="js-issues" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Issues:</h4>
<ul id="js-issues-list" style="margin: 5px 0; padding-left: 20px;"></ul>
</div>
</div>
<div id="js-table-container" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Scripts:</h4>
<div id="js-table" style="max-height: 300px; overflow-y: auto;"></div>
</div>
<div id="long-tasks-container" style="margin-top: 15px; display: none;">
<h4 style="margin: 5px 0; font-size: 13px;">Long Tasks:</h4>
<div id="long-tasks" style="max-height: 150px; overflow-y: auto;"></div>
</div>
`;
document.body.appendChild(debugPanel);
// دکمه بستن پنل
document.getElementById('js-debug-close').addEventListener('click', function() {
debugPanel.style.display = 'none';
});
// بررسی اسکریپتها
const scripts = Array.from(document.querySelectorAll('script[src]'));
const jsIssuesList = document.getElementById('js-issues-list');
const jsSummary = document.getElementById('js-summary');
const jsTable = document.getElementById('js-table');
const longTasksContainer = document.getElementById('long-tasks-container');
const longTasksList = document.getElementById('long-tasks');
// آمار اسکریپتها
const inlineScripts = document.querySelectorAll('script:not([src])').length;
const asyncScripts = scripts.filter(script => script.async).length;
const deferScripts = scripts.filter(script => script.defer).length;
const blockingScripts = scripts.filter(script => !script.async && !script.defer).length;
// اندازه کل اسکریپتها
let totalJsSize = 0;
let loadedScripts = 0;
// نمایش آمار اولیه
jsSummary.innerHTML = `
<div><strong>Total External Scripts:</strong> ${scripts.length}</div>
<div><strong>Inline Scripts:</strong> ${inlineScripts}</div>
<div><strong>Async Scripts:</strong> ${asyncScripts}</div>
<div><strong>Defer Scripts:</strong> ${deferScripts}</div>
<div><strong>Blocking Scripts:</strong> ${blockingScripts}</div>
<div id="js-total-size"><strong>Total JS Size:</strong> Calculating...</div>
`;
// ثبت مشکلات
if (blockingScripts > 3) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">${blockingScripts} render-blocking scripts.</span> Use async or defer for non-critical scripts.`;
jsIssuesList.appendChild(li);
}
if (scripts.length > 15) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">Too many scripts (${scripts.length}).</span> Consider combining scripts or using a module bundler.`;
jsIssuesList.appendChild(li);
}
// تابع برای دریافت اندازه فایل
function getFileSize(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const size = xhr.getResponseHeader('Content-Length');
callback(size ? parseInt(size) : 'Unknown');
} else {
callback('Error');
}
}
};
xhr.send(null);
}
// جدول اسکریپتها
scripts.forEach((script, index) => {
const row = document.createElement('div');
row.className = 'js-row';
row.style.display = 'flex';
row.style.borderBottom = '1px solid #444';
row.style.padding = '5px 0';
const src = script.src;
const isAsync = script.async;
const isDefer = script.defer;
const isBlocking = !isAsync && !isDefer;
row.innerHTML = `
<div style="flex: 0 0 30px; text-align: center;">${index + 1}</div>
<div style="flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${src}">${src.split('/').pop()}</div>
<div style="flex: 0 0 60px; text-align: center; color: ${isAsync ? '#0cce6b' : '#777'};">async</div>
<div style="flex: 0 0 60px; text-align: center; color: ${isDefer ? '#0cce6b' : '#777'};">defer</div>
<div style="flex: 0 0 70px; text-align: center; color: ${isBlocking ? '#ff4e42' : '#0cce6b'};">${isBlocking ? 'blocking' : 'non-blocking'}</div>
`;
jsTable.appendChild(row);
// دریافت اندازه فایل
getFileSize(src, function(size) {
const sizeCell = document.createElement('div');
sizeCell.style.flex = '0 0 70px';
sizeCell.style.textAlign = 'right';
if (size !== 'Error' && size !== 'Unknown') {
const sizeKB = Math.round(size / 1024);
sizeCell.textContent = sizeKB + ' KB';
// هشدار برای اسکریپتهای بزرگ
if (sizeKB > 100) {
sizeCell.style.color = '#ff4e42';
// اضافه کردن به لیست مشکلات اگر اسکریپت بزرگ و blocking است
if (isBlocking && !jsIssuesList.querySelector(`[data-script="${src}"]`)) {
const li = document.createElement('li');
li.dataset.script = src;
li.innerHTML = `<span style="color: #ff4e42;">Large blocking script: ${src.split('/').pop()} (${sizeKB} KB).</span> Consider using async/defer or code splitting.`;
jsIssuesList.appendChild(li);
}
}
// بروزرسانی اندازه کل
totalJsSize += size;
loadedScripts++;
if (loadedScripts === scripts.length) {
document.getElementById('js-total-size').innerHTML = `<strong>Total JS Size:</strong> ${Math.round(totalJsSize / 1024)} KB`;
if (totalJsSize > 1024 * 1024) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">Total JavaScript size is large: ${Math.round(totalJsSize / 1024 / 1024 * 100) / 100} MB.</span> Consider code splitting and lazy loading.`;
jsIssuesList.appendChild(li);
}
}
} else {
sizeCell.textContent = size;
loadedScripts++;
}
row.appendChild(sizeCell);
});
});
// اضافه کردن هدر جدول
const tableHeader = document.createElement('div');
tableHeader.style.display = 'flex';
tableHeader.style.borderBottom = '2px solid #666';
tableHeader.style.fontWeight = 'bold';
tableHeader.style.padding = '5px 0';
tableHeader.innerHTML = `
<div style="flex: 0 0 30px; text-align: center;">#</div>
<div style="flex: 1;">Filename</div>
<div style="flex: 0 0 60px; text-align: center;">Async</div>
<div style="flex: 0 0 60px; text-align: center;">Defer</div>
<div style="flex: 0 0 70px; text-align: center;">Type</div>
<div style="flex: 0 0 70px; text-align: right;">Size</div>
`;
jsTable.insertBefore(tableHeader, jsTable.firstChild);
// بررسی Long Tasks
if ('PerformanceObserver' in window && PerformanceObserver.supportedEntryTypes.includes('longtask')) {
const longTasks = [];
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
longTasks.push({
duration: entry.duration,
startTime: entry.startTime,
name: entry.name
});
// نمایش Long Tasks
longTasksContainer.style.display = 'block';
const taskRow = document.createElement('div');
taskRow.style.display = 'flex';
taskRow.style.borderBottom = '1px solid #444';
taskRow.style.padding = '5px 0';
taskRow.innerHTML = `
<div style="flex: 0 0 80px; text-align: right;">${Math.round(entry.startTime)} ms</div>
<div style="flex: 0 0 80px; text-align: right; color: ${entry.duration > 100 ? '#ff4e42' : '#ffa400'};">${Math.round(entry.duration)} ms</div>
<div style="flex: 1;">${entry.name}</div>
`;
longTasksList.appendChild(taskRow);
// اضافه کردن به لیست مشکلات اگر طولانی است
if (entry.duration > 100 && !jsIssuesList.querySelector(`[data-task="${entry.startTime}"]`)) {
const li = document.createElement('li');
li.dataset.task = entry.startTime;
li.innerHTML = `<span style="color: #ff4e42;">Long task detected: ${Math.round(entry.duration)} ms at ${Math.round(entry.startTime)} ms.</span> This can cause input delay (FID).`;
jsIssuesList.appendChild(li);
}
});
});
observer.observe({ entryTypes: ['longtask'] });
// اضافه کردن هدر جدول Long Tasks
const longTasksHeader = document.createElement('div');
longTasksHeader.style.display = 'flex';
longTasksHeader.style.borderBottom = '2px solid #666';
longTasksHeader.style.fontWeight = 'bold';
longTasksHeader.style.padding = '5px 0';
longTasksHeader.innerHTML = `
<div style="flex: 0 0 80px; text-align: right;">Start Time</div>
<div style="flex: 0 0 80px; text-align: right;">Duration</div>
<div style="flex: 1;">Name</div>
`;
longTasksList.appendChild(longTasksHeader);
} else {
// اگر Long Task API پشتیبانی نمیشود
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ffa400;">Long Task API is not supported in this browser.</span> Cannot detect JavaScript execution issues.`;
jsIssuesList.appendChild(li);
}
// بررسی تداخل با thread اصلی
setTimeout(() => {
// آزمایش تأخیر در thread اصلی
const mainThreadTest = () => {
const start = performance.now();
// انجام یک عملیات ساده
let sum = 0;
for (let i = 0; i < 1000; i++) {
sum += i;
}
const end = performance.now();
const delay = end - start;
// اگر تأخیر بیش از 5ms است، مشکلی در thread اصلی وجود دارد
if (delay > 5) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">Main thread is busy: ${Math.round(delay)} ms delay for a simple operation.</span> This can cause poor responsiveness.`;
jsIssuesList.appendChild(li);
}
};
// اجرای تست چند بار
for (let i = 0; i < 5; i++) {
setTimeout(mainThreadTest, i * 1000);
}
}, 3000);
});
</script>
<?php
}
}
add_action('wp_footer', 'javascript_optimization_debugger', 999);
// اضافه کردن لینک به پنل مدیریت
function add_js_debugger_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Debug JavaScript optimization issues: <a href="' . home_url('?debug_js=1') . '" target="_blank" class="button button-primary">Launch JS Debugger</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_js_debugger_link');
عیبیابی و رفع مشکلات LCP
1. شناسایی و رفع مشکلات TTFB بالا
PHPfunction diagnose_ttfb_issues() {
if (!is_admin() && (current_user_can('administrator') || isset($_GET['diagnose_ttfb']))) {
// ثبت زمان شروع
$start_time = microtime(true);
// ثبت زمانهای مهم
add_action('template_redirect', function() use ($start_time) {
$GLOBALS['ttfb_diagnostics']['template_redirect'] = microtime(true) - $start_time;
}, 0);
add_action('wp', function() use ($start_time) {
$GLOBALS['ttfb_diagnostics']['wp'] = microtime(true) - $start_time;
}, 0);
add_action('wp_head', function() use ($start_time) {
$GLOBALS['ttfb_diagnostics']['wp_head_start'] = microtime(true) - $start_time;
}, 0);
add_action('wp_head', function() use ($start_time) {
$GLOBALS['ttfb_diagnostics']['wp_head_end'] = microtime(true) - $start_time;
}, 999);
add_action('wp_footer', function() use ($start_time) {
$GLOBALS['ttfb_diagnostics']['wp_footer_start'] = microtime(true) - $start_time;
}, 0);
add_action('wp_footer', function() use ($start_time) {
$GLOBALS['ttfb_diagnostics']['wp_footer_end'] = microtime(true) - $start_time;
// ثبت تعداد کوئریهای دیتابیس
global $wpdb;
$GLOBALS['ttfb_diagnostics']['db_queries'] = $wpdb->num_queries;
// ثبت مصرف حافظه
$GLOBALS['ttfb_diagnostics']['memory_usage'] = memory_get_peak_usage(true) / 1024 / 1024;
// ثبت زمان کل
$GLOBALS['ttfb_diagnostics']['total_time'] = microtime(true) - $start_time;
// نمایش نتایج
?>
<div id="ttfb-diagnostic-panel" style="position: fixed; bottom: 0; left: 0; width: 350px; background-color: rgba(0, 0, 0, 0.8); color: white; padding: 15px; z-index: 9999; font-size: 12px; font-family: monospace; border-radius: 0 5px 0 0; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); max-height: 80vh; overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 14px;">TTFB Diagnostics</h3>
<button id="ttfb-diagnostic-close" style="background: none; border: none; color: white; cursor: pointer;">×</button>
</div>
<div style="margin-bottom: 15px;">
<div><strong>Total Time:</strong> <?php echo round($GLOBALS['ttfb_diagnostics']['total_time'] * 1000, 2); ?> ms</div>
<div><strong>Database Queries:</strong> <?php echo $GLOBALS['ttfb_diagnostics']['db_queries']; ?></div>
<div><strong>Memory Usage:</strong> <?php echo round($GLOBALS['ttfb_diagnostics']['memory_usage'], 2); ?> MB</div>
</div>
<h4 style="margin: 5px 0; font-size: 13px;">Execution Timeline:</h4>
<div style="height: 30px; background-color: #444; position: relative; margin-bottom: 10px; border-radius: 3px;">
<?php
$total_time = $GLOBALS['ttfb_diagnostics']['total_time'];
// نمایش زمانهای مهم
$events = array(
'template_redirect' => '#0cce6b',
'wp' => '#0cce6b',
'wp_head_start' => '#ffa400',
'wp_head_end' => '#ffa400',
'wp_footer_start' => '#ff4e42',
'wp_footer_end' => '#ff4e42'
);
foreach ($events as $event => $color) {
$time = $GLOBALS['ttfb_diagnostics'][$event];
$percentage = ($time / $total_time) * 100;
echo '<div style="position: absolute; left: ' . $percentage . '%; top: 0; bottom: 0; width: 2px; background-color: ' . $color . ';"></div>';
}
?>
</div>
<div>
<?php
// نمایش جزئیات زمانها
$phases = array(
'WordPress Core' => array('start' => 0, 'end' => $GLOBALS['ttfb_diagnostics']['template_redirect']),
'Template Setup' => array('start' => $GLOBALS['ttfb_diagnostics']['template_redirect'], 'end' => $GLOBALS['ttfb_diagnostics']['wp']),
'wp_head Processing' => array('start' => $GLOBALS['ttfb_diagnostics']['wp_head_start'], 'end' => $GLOBALS['ttfb_diagnostics']['wp_head_end']),
'Content Rendering' => array('start' => $GLOBALS['ttfb_diagnostics']['wp_head_end'], 'end' => $GLOBALS['ttfb_diagnostics']['wp_footer_start']),
'wp_footer Processing' => array('start' => $GLOBALS['ttfb_diagnostics']['wp_footer_start'], 'end' => $GLOBALS['ttfb_diagnostics']['wp_footer_end'])
);
foreach ($phases as $phase => $times) {
$duration = $times['end'] - $times['start'];
$percentage = ($duration / $total_time) * 100;
$color = $duration > 0.1 ? '#ff4e42' : ($duration > 0.05 ? '#ffa400' : '#0cce6b');
echo '<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">';
echo '<div>' . $phase . ':</div>';
echo '<div style="color: ' . $color . ';">' . round($duration * 1000, 2) . ' ms (' . round($percentage, 1) . '%)</div>';
echo '</div>';
}
?>
</div>
<h4 style="margin: 15px 0 5px; font-size: 13px;">Issues & Recommendations:</h4>
<ul style="margin: 5px 0; padding-left: 20px;">
<?php
// تشخیص مشکلات
if ($GLOBALS['ttfb_diagnostics']['total_time'] > 0.5) {
echo '<li style="color: #ff4e42;">High TTFB: ' . round($GLOBALS['ttfb_diagnostics']['total_time'] * 1000, 2) . ' ms. Consider server-side caching.</li>';
}
if ($GLOBALS['ttfb_diagnostics']['db_queries'] > 50) {
echo '<li style="color: #ff4e42;">High number of database queries: ' . $GLOBALS['ttfb_diagnostics']['db_queries'] . '. Use object caching.</li>';
}
if ($GLOBALS['ttfb_diagnostics']['memory_usage'] > 32) {
echo '<li style="color: #ff4e42;">High memory usage: ' . round($GLOBALS['ttfb_diagnostics']['memory_usage'], 2) . ' MB. Check for inefficient plugins.</li>';
}
$wp_head_time = $GLOBALS['ttfb_diagnostics']['wp_head_end'] - $GLOBALS['ttfb_diagnostics']['wp_head_start'];
if ($wp_head_time > 0.1) {
echo '<li style="color: #ff4e42;">Slow wp_head processing: ' . round($wp_head_time * 1000, 2) . ' ms. Check for inefficient hooks.</li>';
}
// پیشنهادات
echo '<li style="color: #0cce6b;">Enable page caching for faster TTFB.</li>';
if ($GLOBALS['ttfb_diagnostics']['db_queries'] > 20) {
echo '<li style="color: #0cce6b;">Implement object caching with Redis or Memcached.</li>';
}
if (version_compare(PHP_VERSION, '7.4', '<')) {
echo '<li style="color: #0cce6b;">Upgrade to PHP 7.4 or higher for better performance.</li>';
}
?>
</ul>
<script>
document.getElementById('ttfb-diagnostic-close').addEventListener('click', function() {
document.getElementById('ttfb-diagnostic-panel').style.display = 'none';
});
</script>
</div>
<?php
}, 999);
// ذخیره آرایه تشخیص
$GLOBALS['ttfb_diagnostics'] = array();
}
}
add_action('init', 'diagnose_ttfb_issues', 0);
// اضافه کردن لینک به پنل مدیریت
function add_ttfb_diagnostic_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Diagnose TTFB issues: <a href="' . home_url('?diagnose_ttfb=1') . '" target="_blank" class="button button-primary">Launch TTFB Diagnostics</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_ttfb_diagnostic_link');
2. شناسایی و رفع منابع مسدودکننده رندر
PHPfunction diagnose_render_blocking_resources() {
if (!is_admin() && (current_user_can('administrator') || isset($_GET['diagnose_render_blocking']))) {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// تحلیل منابع مسدودکننده رندر
function analyzeRenderBlockingResources() {
// دریافت تمام منابع
const resources = performance.getEntriesByType('resource');
const navigationStart = performance.getEntriesByType('navigation')[0].startTime;
const domContentLoaded = performance.getEntriesByType('navigation')[0].domContentLoadedEventStart;
// فیلتر کردن منابع مسدودکننده رندر
const renderBlockingResources = resources.filter(resource => {
return (resource.initiatorType === 'link' || resource.initiatorType === 'script') &&
!resource.async &&
resource.responseEnd < domContentLoaded &&
resource.startTime < domContentLoaded;
});
// مرتبسازی بر اساس زمان شروع
renderBlockingResources.sort((a, b) => a.startTime - b.startTime);
return {
renderBlockingResources,
navigationStart,
domContentLoaded
};
}
const analysis = analyzeRenderBlockingResources();
// ایجاد پنل دیباگ
const debugPanel = document.createElement('div');
debugPanel.id = 'render-blocking-panel';
debugPanel.style.position = 'fixed';
debugPanel.style.top = '32px';
debugPanel.style.left = '0';
debugPanel.style.width = '400px';
debugPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
debugPanel.style.color = 'white';
debugPanel.style.padding = '15px';
debugPanel.style.zIndex = '9999';
debugPanel.style.fontSize = '12px';
debugPanel.style.fontFamily = 'monospace';
debugPanel.style.borderRadius = '0 0 5px 0';
debugPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
debugPanel.style.maxHeight = '80vh';
debugPanel.style.overflowY = 'auto';
debugPanel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 14px;">Render Blocking Resources</h3>
<button id="render-blocking-close" style="background: none; border: none; color: white; cursor: pointer;">×</button>
</div>
<div style="margin-bottom: 15px;">
<div><strong>DOMContentLoaded:</strong> ${Math.round(analysis.domContentLoaded)} ms</div>
<div><strong>Render Blocking Resources:</strong> ${analysis.renderBlockingResources.length}</div>
</div>
`;
// نمایش نمودار آبشاری
if (analysis.renderBlockingResources.length > 0) {
debugPanel.innerHTML += `<h4 style="margin: 5px 0; font-size: 13px;">Resource Waterfall:</h4>`;
const waterfallContainer = document.createElement('div');
waterfallContainer.style.position = 'relative';
waterfallContainer.style.height = `${analysis.renderBlockingResources.length * 25 + 30}px`;
waterfallContainer.style.marginBottom = '15px';
// خط DOMContentLoaded
const dclLine = document.createElement('div');
dclLine.style.position = 'absolute';
dclLine.style.top = '0';
dclLine.style.bottom = '0';
dclLine.style.left = `${(analysis.domContentLoaded / analysis.domContentLoaded) * 100}%`;
dclLine.style.width = '2px';
dclLine.style.backgroundColor = '#ff4e42';
dclLine.style.zIndex = '1';
const dclLabel = document.createElement('div');
dclLabel.style.position = 'absolute';
dclLabel.style.top = '0';
dclLabel.style.left = `${(analysis.domContentLoaded / analysis.domContentLoaded) * 100}%`;
dclLabel.style.transform = 'translateX(-50%)';
dclLabel.style.backgroundColor = '#ff4e42';
dclLabel.style.color = 'white';
dclLabel.style.padding = '2px 5px';
dclLabel.style.borderRadius = '3px';
dclLabel.style.fontSize = '10px';
dclLabel.textContent = 'DCL';
waterfallContainer.appendChild(dclLine);
waterfallContainer.appendChild(dclLabel);
// نمایش منابع
analysis.renderBlockingResources.forEach((resource, index) => {
const resourceBar = document.createElement('div');
resourceBar.style.position = 'absolute';
resourceBar.style.top = `${index * 25 + 30}px`;
resourceBar.style.height = '20px';
resourceBar.style.left = `${(resource.startTime / analysis.domContentLoaded) * 100}%`;
resourceBar.style.width = `${((resource.responseEnd - resource.startTime) / analysis.domContentLoaded) * 100}%`;
resourceBar.style.backgroundColor = resource.initiatorType === 'link' ? '#0cce6b' : '#ffa400';
resourceBar.style.borderRadius = '3px';
resourceBar.style.padding = '0 5px';
resourceBar.style.overflow = 'hidden';
resourceBar.style.textOverflow = 'ellipsis';
resourceBar.style.whiteSpace = 'nowrap';
resourceBar.style.fontSize = '10px';
resourceBar.style.lineHeight = '20px';
resourceBar.style.color = 'black';
resourceBar.textContent = resource.name.split('/').pop();
resourceBar.title = `${resource.name}\nStart: ${Math.round(resource.startTime)} ms\nEnd: ${Math.round(resource.responseEnd)} ms\nDuration: ${Math.round(resource.duration)} ms`;
const resourceLabel = document.createElement('div');
resourceLabel.style.position = 'absolute';
resourceLabel.style.top = `${index * 25 + 30}px`;
resourceLabel.style.height = '20px';
resourceLabel.style.right = '100%';
resourceLabel.style.marginRight = '10px';
resourceLabel.style.lineHeight = '20px';
resourceLabel.style.whiteSpace = 'nowrap';
resourceLabel.style.fontSize = '10px';
resourceLabel.textContent = resource.initiatorType;
waterfallContainer.appendChild(resourceBar);
waterfallContainer.appendChild(resourceLabel);
});
debugPanel.appendChild(waterfallContainer);
}
// نمایش توصیهها
debugPanel.innerHTML += `<h4 style="margin: 15px 0 5px; font-size: 13px;">Recommendations:</h4>`;
const recommendationsContainer = document.createElement('ul');
recommendationsContainer.style.margin = '5px 0';
recommendationsContainer.style.paddingLeft = '20px';
// CSS files
const cssFiles = analysis.renderBlockingResources.filter(r => r.initiatorType === 'link');
if (cssFiles.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<strong>${cssFiles.length} blocking CSS files:</strong> Use Critical CSS and preload.`;
recommendationsContainer.appendChild(li);
// جزئیات CSS
cssFiles.forEach(file => {
const subItem = document.createElement('li');
subItem.style.fontSize = '11px';
subItem.style.marginTop = '3px';
const fileName = file.name.split('/').pop();
subItem.innerHTML = `<code>${fileName}</code>: `;
// پیشنهادات خاص
if (fileName.includes('woocommerce') && !window.location.pathname.includes('/shop')) {
subItem.innerHTML += 'Load only on WooCommerce pages.';
} else if (fileName.includes('slider') && !document.querySelector('.slider')) {
subItem.innerHTML += 'Load only when slider is present.';
} else {
subItem.innerHTML += 'Extract critical CSS.';
}
recommendationsContainer.appendChild(subItem);
});
}
// JS files
const jsFiles = analysis.renderBlockingResources.filter(r => r.initiatorType === 'script');
if (jsFiles.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<strong>${jsFiles.length} blocking JS files:</strong> Use defer or async.`;
recommendationsContainer.appendChild(li);
// جزئیات JS
jsFiles.forEach(file => {
const subItem = document.createElement('li');
subItem.style.fontSize = '11px';
subItem.style.marginTop = '3px';
const fileName = file.name.split('/').pop();
subItem.innerHTML = `<code>${fileName}</code>: `;
// پیشنهادات خاص
if (fileName.includes('jquery')) {
subItem.innerHTML += 'Consider if jQuery is necessary or load with defer.';
} else if (fileName.includes('analytics') || fileName.includes('gtag') || fileName.includes('pixel')) {
subItem.innerHTML += 'Load with async.';
} else {
subItem.innerHTML += 'Add defer attribute.';
}
recommendationsContainer.appendChild(subItem);
});
}
// پیشنهادات کلی
const generalLi = document.createElement('li');
generalLi.innerHTML = '<strong>General recommendations:</strong>';
recommendationsContainer.appendChild(generalLi);
const generalSubItems = [
'Implement HTTP/2 to load resources in parallel.',
'Use resource hints (preload, preconnect) for critical resources.',
'Minimize the number of critical resources.',
'Inline critical CSS and defer non-critical CSS.'
];
generalSubItems.forEach(tip => {
const subItem = document.createElement('li');
subItem.style.fontSize = '11px';
subItem.style.marginTop = '3px';
subItem.textContent = tip;
recommendationsContainer.appendChild(subItem);
});
debugPanel.appendChild(recommendationsContainer);
// کد اصلاح
debugPanel.innerHTML += `<h4 style="margin: 15px 0 5px; font-size: 13px;">Fix Code Samples:</h4>`;
const codeContainer = document.createElement('div');
codeContainer.style.backgroundColor = '#222';
codeContainer.style.padding = '10px';
codeContainer.style.borderRadius = '3px';
codeContainer.style.fontSize = '11px';
codeContainer.style.overflowX = 'auto';
codeContainer.innerHTML = `
<p style="margin: 0 0 5px; color: #aaa;">For CSS files:</p>
<pre style="margin: 0 0 10px; white-space: pre-wrap;"><link rel="preload" href="style.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="style.css"></noscript></pre>
<p style="margin: 5px 0; color: #aaa;">For JavaScript files:</p>
<pre style="margin: 0 0 10px; white-space: pre-wrap;"><script src="script.js" defer></script></pre>
<p style="margin: 5px 0; color: #aaa;">PHP Code for WordPress:</p>
<pre style="margin: 0; white-space: pre-wrap;">function optimize_scripts_styles() {
// Defer JavaScript
function defer_scripts($tag, $handle, $src) {
if (is_admin()) return $tag;
// Don't defer these scripts
$do_not_defer = array('jquery');
if (in_array($handle, $do_not_defer)) {
return $tag;
}
return str_replace(' src', ' defer src', $tag);
}
add_filter('script_loader_tag', 'defer_scripts', 10, 3);
// Preload CSS
function preload_styles($html, $handle, $href, $media) {
if (is_admin()) return $html;
$html = '<link rel="preload" href="' . $href . '" as="style" onload="this.onload=null;this.rel=\'stylesheet\'">';
$html .= '<noscript><link rel="stylesheet" href="' . $href . '"></noscript>';
return $html;
}
add_filter('style_loader_tag', 'preload_styles', 10, 4);
}
add_action('init', 'optimize_scripts_styles');</pre>
`;
debugPanel.appendChild(codeContainer);
document.body.appendChild(debugPanel);
// دکمه بستن پنل
document.getElementById('render-blocking-close').addEventListener('click', function() {
debugPanel.style.display = 'none';
});
});
</script>
<?php
}
}
add_action('wp_footer', 'diagnose_render_blocking_resources', 999);
// اضافه کردن لینک به پنل مدیریت
function add_render_blocking_diagnostic_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Diagnose render blocking resources: <a href="' . home_url('?diagnose_render_blocking=1') . '" target="_blank" class="button button-primary">Launch Diagnostics</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_render_blocking_diagnostic_link');
عیبیابی و رفع مشکلات CLS
PHPfunction diagnose_cls_issues() {
if (!is_admin() && (current_user_can('administrator') || isset($_GET['diagnose_cls']))) {
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// ایجاد پنل دیباگ
const debugPanel = document.createElement('div');
debugPanel.id = 'cls-debug-panel';
debugPanel.style.position = 'fixed';
debugPanel.style.bottom = '0';
debugPanel.style.right = '0';
debugPanel.style.width = '350px';
debugPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
debugPanel.style.color = 'white';
debugPanel.style.padding = '15px';
debugPanel.style.zIndex = '9999';
debugPanel.style.fontSize = '12px';
debugPanel.style.fontFamily = 'monospace';
debugPanel.style.borderRadius = '5px 0 0 0';
debugPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
debugPanel.style.maxHeight = '80vh';
debugPanel.style.overflowY = 'auto';
debugPanel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; font-size: 14px;">CLS Diagnostics</h3>
<button id="cls-debug-close" style="background: none; border: none; color: white; cursor: pointer;">×</button>
</div>
<div id="cls-stats">
<div id="cls-value">CLS: Measuring...</div>
<div id="cls-status" style="margin-bottom: 10px;"></div>
</div>
<div id="cls-shifts-container" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Layout Shifts:</h4>
<div id="cls-shifts" style="max-height: 200px; overflow-y: auto;"></div>
</div>
<div id="cls-issues-container" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Issues Found:</h4>
<ul id="cls-issues" style="margin: 5px 0; padding-left: 20px;"></ul>
</div>
<div id="cls-recommendations-container" style="margin-top: 15px;">
<h4 style="margin: 5px 0; font-size: 13px;">Recommendations:</h4>
<ul id="cls-recommendations" style="margin: 5px 0; padding-left: 20px;"></ul>
</div>
<div style="margin-top: 15px; text-align: center;">
<button id="highlight-cls-elements" style="background: #0073aa; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;">Highlight Problem Elements</button>
</div>
`;
document.body.appendChild(debugPanel);
// دکمه بستن پنل
document.getElementById('cls-debug-close').addEventListener('click', function() {
debugPanel.style.display = 'none';
});
// متغیرهای مورد نیاز
const clsValue = document.getElementById('cls-value');
const clsStatus = document.getElementById('cls-status');
const clsShifts = document.getElementById('cls-shifts');
const clsIssues = document.getElementById('cls-issues');
const clsRecommendations = document.getElementById('cls-recommendations');
// ثبت مشکلات
const issues = [];
const shiftElements = [];
// بررسی تصاویر بدون ابعاد
const imagesWithoutDimensions = Array.from(document.querySelectorAll('img')).filter(img => {
return !img.hasAttribute('width') || !img.hasAttribute('height');
});
if (imagesWithoutDimensions.length > 0) {
issues.push({
type: 'images_without_dimensions',
elements: imagesWithoutDimensions,
message: `${imagesWithoutDimensions.length} images without width/height attributes`
});
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">${imagesWithoutDimensions.length} images without dimensions.</span> This can cause layout shifts when images load.`;
clsIssues.appendChild(li);
// پیشنهاد
const recommendation = document.createElement('li');
recommendation.innerHTML = `Add width and height attributes to all images. Example: <code><img src="image.jpg" width="800" height="600" alt="Description"></code>`;
clsRecommendations.appendChild(recommendation);
// کد PHP پیشنهادی
const phpCode = document.createElement('li');
phpCode.innerHTML = `
PHP code to fix:
<pre style="background: #222; padding: 5px; margin: 5px 0; font-size: 11px; border-radius: 3px;">function add_image_dimensions($content) {
return preg_replace_callback('/<img(.*?)>/', function($matches) {
$img_tag = $matches[0];
// اگر width و height وجود دارند، تغییری ایجاد نکن
if (strpos($img_tag, 'width=') !== false && strpos($img_tag, 'height=') !== false) {
return $img_tag;
}
// استخراج URL تصویر
preg_match('/src="([^"]*)"/', $img_tag, $src_match);
if (empty($src_match[1])) return $img_tag;
$src = $src_match[1];
$upload_dir = wp_upload_dir();
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $src);
if (file_exists($file_path)) {
list($width, $height) = getimagesize($file_path);
return str_replace('<img', '<img width="' . $width . '" height="' . $height . '"', $img_tag);
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'add_image_dimensions');</pre>
`;
clsRecommendations.appendChild(phpCode);
}
// بررسی فونتهای وب
const webFonts = Array.from(document.querySelectorAll('link[rel="stylesheet"][href*="fonts.googleapis.com"], link[rel="stylesheet"][href*="fonts."]'));
if (webFonts.length > 0) {
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ffa400;">${webFonts.length} web font stylesheets detected.</span> Web fonts can cause layout shifts.`;
clsIssues.appendChild(li);
// پیشنهاد
const recommendation = document.createElement('li');
recommendation.innerHTML = `Use <code>font-display: swap</code> and preload critical fonts.`;
clsRecommendations.appendChild(recommendation);
// کد پیشنهادی
const fontCode = document.createElement('li');
fontCode.innerHTML = `
Code to fix:
<pre style="background: #222; padding: 5px; margin: 5px 0; font-size: 11px; border-radius: 3px;">// Preload fonts
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
// CSS
@font-face {
font-family: 'Your Font';
src: url('font.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}</pre>
`;
clsRecommendations.appendChild(fontCode);
}
// بررسی تبلیغات و محتوای متغیر
const dynamicContainers = Array.from(document.querySelectorAll('div[id*="ad"], div[class*="ad"], iframe, .widget, .sidebar, [class*="dynamic"], [id*="dynamic"]'));
if (dynamicContainers.length > 0) {
const containersWithoutHeight = dynamicContainers.filter(container => {
const style = window.getComputedStyle(container);
return style.height === 'auto' || style.minHeight === '0px' || style.minHeight === 'auto';
});
if (containersWithoutHeight.length > 0) {
issues.push({
type: 'dynamic_containers',
elements: containersWithoutHeight,
message: `${containersWithoutHeight.length} dynamic containers without fixed height`
});
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ffa400;">${containersWithoutHeight.length} dynamic content containers without fixed height.</span> This can cause layout shifts when content loads.`;
clsIssues.appendChild(li);
// پیشنهاد
const recommendation = document.createElement('li');
recommendation.innerHTML = `Set min-height for ad containers and dynamic content areas.`;
clsRecommendations.appendChild(recommendation);
// کد پیشنهادی
const containerCode = document.createElement('li');
containerCode.innerHTML = `
CSS to fix:
<pre style="background: #222; padding: 5px; margin: 5px 0; font-size: 11px; border-radius: 3px;">.ad-container, .dynamic-content {
min-height: 250px; /* Set appropriate height */
width: 100%;
}
.sidebar-widget {
min-height: 100px; /* Set appropriate height */
}</pre>
`;
clsRecommendations.appendChild(containerCode);
}
}
// هایلایت عناصر مشکلدار
document.getElementById('highlight-cls-elements').addEventListener('click', function() {
const isHighlighted = this.dataset.highlighted === 'true';
if (isHighlighted) {
// حذف هایلایت
issues.forEach(issue => {
issue.elements.forEach(element => {
element.style.outline = '';
element.title = element.dataset.originalTitle || '';
});
});
shiftElements.forEach(element => {
if (element) {
element.style.outline = '';
element.title = element.dataset.originalTitle || '';
}
});
this.textContent = 'Highlight Problem Elements';
this.dataset.highlighted = 'false';
} else {
// اضافه کردن هایلایت
issues.forEach(issue => {
issue.elements.forEach(element => {
element.dataset.originalTitle = element.title || '';
element.style.outline = '2px solid #ff4e42';
element.title = issue.message;
});
});
shiftElements.forEach(element => {
if (element) {
element.dataset.originalTitle = element.title || '';
element.style.outline = '2px solid #ffa400';
element.title = 'Layout shift source';
}
});
this.textContent = 'Remove Highlight';
this.dataset.highlighted = 'true';
}
});
// اند ازهگیری CLS
let cumulativeLayoutShift = 0;
// PerformanceObserver برای Layout Shift
if ('PerformanceObserver' in window && PerformanceObserver.supportedEntryTypes.includes('layout-shift')) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// اگر shift قابل توجه است
if (!entry.hadRecentInput && entry.value > 0.01) {
cumulativeLayoutShift += entry.value;
// بروزرسانی مقدار CLS
clsValue.textContent = `CLS: ${cumulativeLayoutShift.toFixed(3)}`;
// بروزرسانی وضعیت
let status, color;
if (cumulativeLayoutShift <= 0.1) {
status = 'Good';
color = '#0cce6b';
} else if (cumulativeLayoutShift <= 0.25) {
status = 'Needs Improvement';
color = '#ffa400';
} else {
status = 'Poor';
color = '#ff4e42';
}
clsStatus.innerHTML = `<span style="color: ${color};">${status}</span>`;
// اضافه کردن به لیست shifts
const shiftItem = document.createElement('div');
shiftItem.style.padding = '5px 0';
shiftItem.style.borderBottom = '1px solid #444';
// زمان shift
const time = Math.round(entry.startTime);
// عناصر درگیر
let elementsInfo = '';
if (entry.sources && entry.sources.length > 0) {
entry.sources.forEach(source => {
if (source.node) {
elementsInfo += getElementDescription(source.node) + ', ';
// اضافه کردن به لیست عناصر shift
if (!shiftElements.includes(source.node)) {
shiftElements.push(source.node);
}
}
});
elementsInfo = elementsInfo.slice(0, -2); // حذف آخرین کاما و فاصله
} else {
elementsInfo = 'Unknown elements';
}
shiftItem.innerHTML = `
<div><strong>Value:</strong> <span style="color: ${entry.value > 0.05 ? '#ff4e42' : '#ffa400'};">${entry.value.toFixed(3)}</span></div>
<div><strong>Time:</strong> ${time}ms</div>
<div><strong>Elements:</strong> ${elementsInfo}</div>
`;
clsShifts.insertBefore(shiftItem, clsShifts.firstChild);
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
} else {
clsValue.textContent = 'CLS: Not supported in this browser';
clsStatus.textContent = 'Layout Shift API not supported';
const li = document.createElement('li');
li.innerHTML = `<span style="color: #ff4e42;">Layout Shift API not supported in this browser.</span> Cannot measure CLS.`;
clsIssues.appendChild(li);
}
// Helper function to get element description
function getElementDescription(element) {
if (!element) return 'Unknown';
let description = element.tagName.toLowerCase();
if (element.id) {
description += `#${element.id}`;
} else if (element.className && typeof element.className === 'string') {
const classes = element.className.trim().split(/\s+/);
if (classes.length > 0 && classes[0]) {
description += `.${classes[0]}`;
}
}
// اضافه کردن ابعاد
const rect = element.getBoundingClientRect();
description += ` (${Math.round(rect.width)}×${Math.round(rect.height)})`;
return description;
}
// پیشنهادات عمومی
const generalRecommendations = [
'Set explicit width and height for all images',
'Use <code>font-display: swap</code> for web fonts',
'Set minimum height for containers that will load dynamic content',
'Use CSS aspect-ratio property for responsive elements',
'Avoid inserting content above existing content',
'Precompute space for dynamic elements'
];
generalRecommendations.forEach(recommendation => {
const li = document.createElement('li');
li.innerHTML = recommendation;
clsRecommendations.appendChild(li);
});
});
</script>
<?php
}
}
add_action('wp_footer', 'diagnose_cls_issues', 999);
// اضافه کردن لینک به پنل مدیریت
function add_cls_diagnostic_link() {
if (current_user_can('administrator')) {
echo '<div class="notice notice-info is-dismissible">';
echo '<p>Diagnose CLS issues: <a href="' . home_url('?diagnose_cls=1') . '" target="_blank" class="button button-primary">Launch CLS Diagnostics</a></p>';
echo '</div>';
}
}
add_action('admin_notices', 'add_cls_diagnostic_link');
برطرف کردن مشکلات رایج
1. رفع مشکلات رایج افزونهها
PHPfunction plugin_conflict_resolver() {
if (is_admin() && current_user_can('administrator')) {
// بررسی افزونههای مشکلساز رایج
$problematic_plugins = array(
'autoptimize' => array(
'name' => 'Autoptimize',
'issues' => array(
'CSS aggregation can cause FOUT (Flash of Unstyled Text)',
'JS aggregation can break some scripts',
'Conflict with other optimization plugins'
),
'solutions' => array(
'Disable CSS aggregation and use Critical CSS instead',
'Exclude problematic scripts from JS optimization',
'Use inline CSS option for above-the-fold CSS'
)
),
'wp-rocket' => array(
'name' => 'WP Rocket',
'issues' => array(
'CSS/JS minification can cause visual glitches',
'Lazy loading can increase CLS',
'File optimization can conflict with CDNs'
),
'solutions' => array(
'Exclude problematic files from minification',
'Enable "Add missing image dimensions" option',
'Adjust preloading settings'
)
),
'elementor' => array(
'name' => 'Elementor',
'issues' => array(
'Large CSS/JS footprint',
'Excessive DOM size',
'Multiple font loading'
),
'solutions' => array(
'Use "Improved Asset Loading" feature',
'Optimize images before uploading',
'Limit the use of custom fonts'
)
),
'woocommerce' => array(
'name' => 'WooCommerce',
'issues' => array(
'Cart fragments AJAX causes high FID',
'Multiple scripts loaded on all pages',
'Large CSS files'
),
'solutions' => array(
'Disable cart fragments on non-shop pages',
'Load WooCommerce scripts only on shop pages',
'Use a WooCommerce performance plugin'
)
),
'revslider' => array(
'name' => 'Revolution Slider',
'issues' => array(
'Large JS libraries',
'High CLS from slider loading',
'Multiple font loading'
),
'solutions' => array(
'Set fixed height for slider containers',
'Optimize and resize slider images',
'Reduce the number of slides and effects'
)
),
'contact-form-7' => array(
'name' => 'Contact Form 7',
'issues' => array(
'Loads scripts on all pages',
'Recaptcha script can slow down pages'
),
'solutions' => array(
'Load CF7 scripts only on pages with forms',
'Consider using a lighter form plugin'
)
)
);
// بررسی افزونههای فعال
$active_plugins = get_option('active_plugins');
$detected_issues = array();
foreach ($active_plugins as $plugin) {
$plugin_slug = explode('/', $plugin)[0];
if (array_key_exists($plugin_slug, $problematic_plugins)) {
$detected_issues[$plugin_slug] = $problematic_plugins[$plugin_slug];
}
}
// نمایش هشدارها
if (!empty($detected_issues) && !isset($_GET['page']) && !isset($_GET['plugin_conflicts_dismissed'])) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<h3>Potential Core Web Vitals Issues Detected</h3>';
echo '<p>The following plugins may impact your Core Web Vitals scores:</p>';
echo '<ul>';
foreach ($detected_issues as $slug => $plugin) {
echo '<li><strong>' . $plugin['name'] . '</strong>: ' . $plugin['issues'][0] . ' <a href="#" class="toggle-plugin-details" data-plugin="' . $slug . '">Show details</a>';
echo '<div class="plugin-details" id="details-' . $slug . '" style="display: none; margin-left: 20px; padding: 10px; background: #f9f9f9; border-left: 4px solid #ffb900;">';
echo '<p><strong>Common Issues:</strong></p>';
echo '<ul>';
foreach ($plugin['issues'] as $issue) {
echo '<li>' . $issue . '</li>';
}
echo '</ul>';
echo '<p><strong>Recommended Solutions:</strong></p>';
echo '<ul>';
foreach ($plugin['solutions'] as $solution) {
echo '<li>' . $solution . '</li>';
}
echo '</ul>';
echo '</div></li>';
}
echo '</ul>';
echo '<p><a href="' . admin_url('tools.php?page=core-web-vitals-fixer') . '" class="button button-primary">Fix Issues</a> <a href="' . add_query_arg('plugin_conflicts_dismissed', '1') . '" class="button">Dismiss</a></p>';
echo '<script>
jQuery(document).ready(function($) {
$(".toggle-plugin-details").click(function(e) {
e.preventDefault();
var plugin = $(this).data("plugin");
$("#details-" + plugin).toggle();
if ($("#details-" + plugin).is(":visible")) {
$(this).text("Hide details");
} else {
$(this).text("Show details");
}
});
});
</script>';
echo '</div>';
}
// صفحه رفع مشکلات
if (isset($_GET['page']) && $_GET['page'] === 'core-web-vitals-fixer') {
// پردازش درخواستها
if (isset($_POST['fix_plugin_issues']) && isset($_POST['plugins'])) {
$plugins_to_fix = $_POST['plugins'];
foreach ($plugins_to_fix as $plugin_slug) {
// اعمال تنظیمات بهینه
switch ($plugin_slug) {
case 'autoptimize':
update_option('autoptimize_css', '0');
update_option('autoptimize_css_defer', '1');
update_option('autoptimize_css_defer_inline', '1');
echo '<div class="notice notice-success"><p>Autoptimize settings optimized: Disabled CSS aggregation, enabled defer loading.</p></div>';
break;
case 'wp-rocket':
if (function_exists('get_rocket_option')) {
update_rocket_option('minify_css', 0);
update_rocket_option('minify_js', 0);
update_rocket_option('dimensions', 1);
echo '<div class="notice notice-success"><p>WP Rocket settings optimized: Disabled CSS/JS minification, enabled image dimensions.</p></div>';
} else {
echo '<div class="notice notice-error"><p>WP Rocket functions not found. Please optimize settings manually.</p></div>';
}
break;
case 'elementor':
update_option('elementor_experiment-e_optimized_assets_loading', 'active');
echo '<div class="notice notice-success"><p>Elementor settings optimized: Enabled improved asset loading.</p></div>';
break;
case 'woocommerce':
// اضافه کردن کد به functions.php برای بهینهسازی WooCommerce
$functions_file = get_stylesheet_directory() . '/functions.php';
$woo_optimization_code = '
// WooCommerce Performance Optimization
function optimize_woocommerce_performance() {
if (!is_woocommerce() && !is_cart() && !is_checkout()) {
// Remove WooCommerce scripts and styles
remove_action("wp_enqueue_scripts", [WC_Frontend_Scripts::class, "load_scripts"]);
remove_action("wp_print_scripts", [WC_Frontend_Scripts::class, "localize_printed_scripts"], 5);
remove_action("wp_print_footer_scripts", [WC_Frontend_Scripts::class, "localize_printed_scripts"], 5);
// Disable cart fragments
wp_dequeue_script("wc-cart-fragments");
}
}
add_action("template_redirect", "optimize_woocommerce_performance");
';
// اضافه کردن کد به فایل functions.php
if (is_writable($functions_file)) {
file_put_contents($functions_file, $woo_optimization_code, FILE_APPEND);
echo '<div class="notice notice-success"><p>WooCommerce optimization code added to functions.php.</p></div>';
} else {
echo '<div class="notice notice-error"><p>Could not write to functions.php. Please add the following code manually:</p>';
echo '<pre>' . htmlspecialchars($woo_optimization_code) . '</pre></div>';
}
break;
case 'contact-form-7':
// اضافه کردن کد به functions.php برای بهینهسازی CF7
$functions_file = get_stylesheet_directory() . '/functions.php';
$cf7_optimization_code = '
// Contact Form 7 Performance Optimization
function optimize_cf7_performance() {
if (!is_page("contact") && !is_page("contact-us")) { // Adjust page slugs as needed
wp_dequeue_script("contact-form-7");
wp_dequeue_style("contact-form-7");
}
}
add_action("wp_enqueue_scripts", "optimize_cf7_performance", 20);
';
// اضافه کردن کد به فایل functions.php
if (is_writable($functions_file)) {
file_put_contents($functions_file, $cf7_optimization_code, FILE_APPEND);
echo '<div class="notice notice-success"><p>Contact Form 7 optimization code added to functions.php.</p></div>';
} else {
echo '<div class="notice notice-error"><p>Could not write to functions.php. Please add the following code manually:</p>';
echo '<pre>' . htmlspecialchars($cf7_optimization_code) . '</pre></div>';
}
break;
}
}
echo '<div class="notice notice-success"><p>Selected plugin optimizations applied. Please test your site to ensure everything works correctly.</p></div>';
}
// نمایش فرم
echo '<div class="wrap">';
echo '<h1>Core Web Vitals Plugin Fixer</h1>';
echo '<p>This tool will help you optimize plugins that may be affecting your Core Web Vitals scores.</p>';
echo '<form method="post">';
echo '<table class="widefat" style="margin-top: 20px;">';
echo '<thead><tr><th>Plugin</th><th>Issues</th><th>Fix</th></tr></thead>';
echo '<tbody>';
foreach ($detected_issues as $slug => $plugin) {
echo '<tr>';
echo '<td><strong>' . $plugin['name'] . '</strong></td>';
echo '<td>';
echo '<ul style="margin: 0;">';
foreach ($plugin['issues'] as $issue) {
echo '<li>' . $issue . '</li>';
}
echo '</ul>';
echo '</td>';
echo '<td><input type="checkbox" name="plugins[]" value="' . $slug . '" checked></td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '<p class="submit"><input type="submit" name="fix_plugin_issues" class="button button-primary" value="Apply Fixes"></p>';
echo '</form>';
echo '<div class="notice notice-warning inline"><p><strong>Note:</strong> Always make a backup before applying fixes. Some changes may require additional configuration.</p></div>';
echo '</div>';
}
}
}
add_action('admin_notices', 'plugin_conflict_resolver');
// اضافه کردن صفحه به منوی ابزارها
function add_core_web_vitals_fixer_page() {
add_submenu_page(
'tools.php',
'Core Web Vitals Fixer',
'Core Web Vitals Fixer',
'manage_options',
'core-web-vitals-fixer',
'__return_false' // محتوا در تابع plugin_conflict_resolver نمایش داده میشود
);
}
add_action('admin_menu', 'add_core_web_vitals_fixer_page');
2. رفع مشکلات رایج قالبها
PHPfunction theme_performance_analyzer() {
if (is_admin() && current_user_can('administrator')) {
// بررسی قالب فعلی
$current_theme = wp_get_theme();
$theme_name = $current_theme->get('Name');
$theme_version = $current_theme->get('Version');
// لیست مشکلات رایج قالبها
$common_theme_issues = array(
'large_css_js' => array(
'test' => function() {
$theme_dir = get_template_directory();
$css_size = 0;
$js_size = 0;
// بررسی فایلهای CSS
foreach (glob($theme_dir . '/**/*.css', GLOB_NOSORT) as $css_file) {
$css_size += filesize($css_file);
}
// بررسی فایلهای JS
foreach (glob($theme_dir . '/**/*.js', GLOB_NOSORT) as $js_file) {
$js_size += filesize($js_file);
}
return array(
'detected' => ($css_size > 500 * 1024 || $js_size > 1024 * 1024),
'css_size' => round($css_size / 1024, 2),
'js_size' => round($js_size / 1024, 2)
);
},
'title' => 'Large CSS/JS Files',
'description' => 'Your theme contains large CSS or JavaScript files that can slow down page loading.',
'solution' => 'Minify CSS/JS files, remove unused styles, and consider splitting large files into smaller ones.'
),
'render_blocking' => array(
'test' => function() {
// بررسی هوکهای wp_head
global $wp_filter;
$blocking_hooks = 0;
if (isset($wp_filter['wp_head'])) {
$blocking_hooks = count($wp_filter['wp_head']->callbacks);
}
return array(
'detected' => $blocking_hooks > 15,
'hooks_count' => $blocking_hooks
);
},
'title' => 'Render-Blocking Resources',
'description' => 'Your theme is adding many resources to the <head> section that block rendering.',
'solution' => 'Use defer for non-critical JavaScript and preload for critical CSS.'
),
'jquery_dependency' => array(
'test' => function() {
// بررسی وابستگی به jQuery
$theme_dir = get_template_directory();
$js_files = glob($theme_dir . '/**/*.js', GLOB_NOSORT);
$jquery_usage = 0;
foreach ($js_files as $js_file) {
$content = file_get_contents($js_file);
if (strpos($content, 'jQuery') !== false || strpos($content, '$.') !== false || strpos($content, 'jquery') !== false) {
$jquery_usage++;
}
}
return array(
'detected' => $jquery_usage > 5,
'jquery_files' => $jquery_usage
);
},
'title' => 'Heavy jQuery Dependency',
'description' => 'Your theme relies heavily on jQuery, which can slow down page loading.',
'solution' => 'Consider using vanilla JavaScript or defer jQuery loading.'
),
'outdated_php' => array(
'test' => function() {
// بررسی سازگاری با PHP جدید
$theme_headers = wp_get_theme()->get('RequiresPHP');
return array(
'detected' => $theme_headers && version_compare($theme_headers, '7.4', '<'),
'required_php' => $theme_headers ?: 'Unknown'
);
},
'title' => 'Outdated PHP Compatibility',
'description' => 'Your theme may not be optimized for modern PHP versions.',
'solution' => 'Update to a theme that supports PHP 7.4+ for better performance.'
),
'responsive_images' => array(
'test' => function() {
// بررسی پشتیبانی از تصاویر ریسپانسیو
$theme_dir = get_template_directory();
$php_files = glob($theme_dir . '/**/*.php', GLOB_NOSORT);
$srcset_usage = 0;
foreach ($php_files as $php_file) {
$content = file_get_contents($php_file);
if (strpos($content, 'srcset') !== false) {
$srcset_usage++;
}
}
return array(
'detected' => $srcset_usage < 2,
'srcset_usage' => $srcset_usage
);
},
'title' => 'Poor Responsive Image Support',
'description' => 'Your theme may not be properly implementing responsive images with srcset.',
'solution' => 'Ensure the theme uses wp_get_attachment_image() or adds srcset attributes to images.'
)
);
// اجرای تستها
$detected_theme_issues = array();
foreach ($common_theme_issues as $key => $issue) {
$result = $issue['test']();
if ($result['detected']) {
$detected_theme_issues[$key] = array_merge($issue, $result);
}
}
// نمایش هشدارها
if (!empty($detected_theme_issues) && !isset($_GET['page']) && !isset($_GET['theme_issues_dismissed'])) {
echo '<div class="notice notice-warning is-dismissible">';
echo '<h3>Theme Performance Issues Detected</h3>';
echo '<p>Your current theme "' . $theme_name . ' v' . $theme_version . '" may have issues affecting Core Web Vitals:</p>';
echo '<ul>';
foreach ($detected_theme_issues as $key => $issue) {
echo '<li><strong>' . $issue['title'] . '</strong>: ' . $issue['description'] . ' <a href="#" class="toggle-theme-issue" data-issue="' . $key . '">Show details</a>';
echo '<div class="theme-issue-details" id="issue-' . $key . '" style="display: none; margin-left: 20px; padding: 10px; background: #f9f9f9; border-left: 4px solid #ffb900;">';
// نمایش جزئیات مشکل
switch ($key) {
case 'large_css_js':
echo '<p>Total CSS size: ' . $issue['css_size'] . ' KB</p>';
echo '<p>Total JS size: ' . $issue['js_size'] . ' KB</p>';
break;
case 'render_blocking':
echo '<p>Number of wp_head hooks: ' . $issue['hooks_count'] . '</p>';
break;
case 'jquery_dependency':
echo '<p>Files using jQuery: ' . $issue['jquery_files'] . '</p>';
break;
case 'outdated_php':
echo '<p>Theme requires PHP: ' . $issue['required_php'] . '</p>';
echo '<p>Current PHP version: ' . PHP_VERSION . '</p>';
break;
case 'responsive_images':
echo '<p>Files using srcset: ' . $issue['srcset_usage'] . '</p>';
break;
}
echo '<p><strong>Solution:</strong> ' . $issue['solution'] . '</p>';
echo '</div></li>';
}
echo '</ul>';
echo '<p><a href="' . admin_url('tools.php?page=theme-performance-fixer') . '" class="button button-primary">Fix Issues</a> <a href="' . add_query_arg('theme_issues_dismissed', '1') . '" class="button">Dismiss</a></p>';
echo '<script>
jQuery(document).ready(function($) {
$(".toggle-theme-issue").click(function(e) {
e.preventDefault();
var issue = $(this).data("issue");
$("#issue-" + issue).toggle();
if ($("#issue-" + issue).is(":visible")) {
$(this).text("Hide details");
} else {
$(this).text("Show details");
}
});
});
</script>';
echo '</div>';
}
// صفحه رفع مشکلات
if (isset($_GET['page']) && $_GET['page'] === 'theme-performance-fixer') {
// پردازش درخواستها
if (isset($_POST['fix_theme_issues']) && isset($_POST['issues'])) {
$issues_to_fix = $_POST['issues'];
// ایجاد child theme اگر وجود ندارد
$theme = wp_get_theme();
$child_theme_exists = false;
if ($theme->parent()) {
// قالب فعلی یک child theme است
$child_theme_exists = true;
$child_theme_dir = get_stylesheet_directory();
} else {
// ایجاد child theme
$parent_theme_name = $theme->get('Name');
$child_theme_name = $parent_theme_name . ' Child';
$child_theme_slug = sanitize_title($child_theme_name);
$child_theme_dir = WP_CONTENT_DIR . '/themes/' . $child_theme_slug;
if (!file_exists($child_theme_dir)) {
mkdir($child_theme_dir, 0755);
// ایجاد فایل style.css
$css_content = "/*
Theme Name: {$child_theme_name}
Theme URI:
Description: A child theme of {$parent_theme_name}
Author:
Author URI:
Template: {$theme->get_stylesheet()}
Version: 1.0.0
*/
/* Import parent theme styles */
@import url('../{$theme->get_stylesheet()}/style.css');
";
file_put_contents($child_theme_dir . '/style.css', $css_content);
// ایجاد فایل functions.php
$functions_content = "<?php
// Child theme functions
function {$child_theme_slug}_enqueue_styles() {
wp_enqueue_style('child-style', get_stylesheet_uri(),
array('parent-style'), wp_get_theme()->get('Version')
);
}
add_action('wp_enqueue_scripts', '{$child_theme_slug}_enqueue_styles');
// Performance optimizations
";
file_put_contents($child_theme_dir . '/functions.php', $functions_content);
echo '<div class="notice notice-success"><p>Child theme created successfully: ' . $child_theme_name . '</p></div>';
echo '<div class="notice notice-warning"><p>Please activate the child theme after applying fixes.</p></div>';
} else {
$child_theme_exists = true;
echo '<div class="notice notice-info"><p>Child theme already exists: ' . $child_theme_name . '</p></div>';
}
}
// اعمال ت غییرات
if ($child_theme_exists) {
$functions_file = $child_theme_dir . '/functions.php';
foreach ($issues_to_fix as $issue) {
switch ($issue) {
case 'large_css_js':
// اضافه کردن کد بهینهسازی CSS/JS
$optimization_code = '
// Optimize CSS and JS loading
function optimize_css_js_loading() {
// Defer non-critical JavaScript
function defer_js($tag, $handle, $src) {
$do_not_defer = array("jquery", "jquery-core");
if (in_array($handle, $do_not_defer)) {
return $tag;
}
return str_replace(\' src\', \' defer src\', $tag);
}
add_filter("script_loader_tag", "defer_js", 10, 3);
// Preload CSS
function preload_css($html, $handle, $href, $media) {
$critical_css = array("main-style", "theme-style");
if (in_array($handle, $critical_css)) {
return $html; // Keep critical CSS as is
}
$html = \'<link rel="preload" href="\' . $href . \'" as="style" onload="this.onload=null;this.rel=\\\'stylesheet\\\'">\';
$html .= \'<noscript><link rel="stylesheet" href="\' . $href . \'"></noscript>\';
return $html;
}
add_filter("style_loader_tag", "preload_css", 10, 4);
}
add_action("init", "optimize_css_js_loading");
';
file_put_contents($functions_file, $optimization_code, FILE_APPEND);
echo '<div class="notice notice-success"><p>Added CSS/JS optimization code to functions.php</p></div>';
break;
case 'jquery_dependency':
// Defer jQuery
$jquery_code = '
// Optimize jQuery loading
function optimize_jquery() {
if (!is_admin()) {
wp_deregister_script("jquery");
wp_register_script("jquery", includes_url("/js/jquery/jquery.js"), false, NULL, true);
wp_enqueue_script("jquery");
}
}
add_action("wp_enqueue_scripts", "optimize_jquery", 1);
';
file_put_contents($functions_file, $jquery_code, FILE_APPEND);
echo '<div class="notice notice-success"><p>Added jQuery optimization code to functions.php</p></div>';
break;
case 'responsive_images':
// اضافه کردن پشتیبانی از تصاویر ریسپانسیو
$responsive_images_code = '
// Add responsive image support
function add_responsive_image_support($content) {
if (empty($content)) {
return $content;
}
$pattern = \'/<img([^>]+)(?:(?:\s+width=["\']([^"\']+)["\'])(?:\s+height=["\']([^"\']+)["\']))([^>]*)>/i\';
$replacement = \'<img$1$4>\';
$content = preg_replace($pattern, $replacement, $content);
// Add srcset to remaining images
$content = preg_replace_callback(
\'/<img([^>]+)>/i\',
function($matches) {
$img_tag = $matches[0];
// Skip if already has srcset
if (strpos($img_tag, \'srcset=\') !== false) {
return $img_tag;
}
// Get src
if (preg_match(\'/src=(["\'])([^"\']+)(["\'])/i\', $img_tag, $src_matches)) {
$src = $src_matches[2];
// Get attachment ID from URL
$attachment_id = attachment_url_to_postid($src);
if ($attachment_id) {
// Get srcset
$image_srcset = wp_get_attachment_image_srcset($attachment_id);
$image_sizes = wp_get_attachment_image_sizes($attachment_id);
if ($image_srcset) {
$img_tag = str_replace(\'<img\', \'<img srcset="\' . $image_srcset . \'" sizes="\' . $image_sizes . \'"\', $img_tag);
}
}
}
return $img_tag;
},
$content
);
return $content;
}
add_filter("the_content", "add_responsive_image_support", 999);
';
file_put_contents($functions_file, $responsive_images_code, FILE_APPEND);
echo '<div class="notice notice-success"><p>Added responsive image support to functions.php</p></div>';
break;
}
}
echo '<div class="notice notice-success"><p>Applied selected theme fixes. Please test your site to ensure everything works correctly.</p></div>';
if (!$theme->parent()) {
echo '<div class="notice notice-warning"><p>Remember to <a href="' . admin_url('themes.php') . '">activate the child theme</a> to apply these changes.</p></div>';
}
}
}
// نمایش فرم
echo '<div class="wrap">';
echo '<h1>Theme Performance Fixer</h1>';
echo '<p>This tool will help you optimize your theme for better Core Web Vitals scores.</p>';
echo '<div class="notice notice-warning inline"><p><strong>Important:</strong> These fixes will be applied to a child theme. If you\'re not already using a child theme, one will be created for you.</p></div>';
echo '<form method="post">';
echo '<table class="widefat" style="margin-top: 20px;">';
echo '<thead><tr><th>Issue</th><th>Description</th><th>Fix</th></tr></thead>';
echo '<tbody>';
foreach ($detected_theme_issues as $key => $issue) {
echo '<tr>';
echo '<td><strong>' . $issue['title'] . '</strong></td>';
echo '<td>' . $issue['description'] . '<br><em>Solution: ' . $issue['solution'] . '</em></td>';
echo '<td><input type="checkbox" name="issues[]" value="' . $key . '" checked></td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '<p class="submit"><input type="submit" name="fix_theme_issues" class="button button-primary" value="Apply Fixes"></p>';
echo '</form>';
echo '<div class="notice notice-warning inline"><p><strong>Note:</strong> Always make a backup before applying fixes. Some changes may require additional configuration.</p></div>';
echo '</div>';
}
}
}
add_action('admin_notices', 'theme_performance_analyzer');
// اضافه کردن صفحه به منوی ابزارها
function add_theme_performance_fixer_page() {
add_submenu_page(
'tools.php',
'Theme Performance Fixer',
'Theme Performance Fixer',
'manage_options',
'theme-performance-fixer',
'__return_false' // محتوا در تابع theme_performance_analyzer نمایش داده میشود
);
}
add_action('admin_menu', 'add_theme_performance_fixer_page');
عیبیابی و رفع مشکلات رایج Core Web Vitals یک فرآیند مداوم است که نیاز به بررسی و بهینهسازی منظم دارد. با استفاده از ابزارهای تشخیصی و روشهای رفع مشکلات ارائه شده در این بخش، میتوانید مشکلات Core Web Vitals را شناسایی و برطرف کنید و به امتیاز 100 در Lighthouse نزدیکتر شوید.
در بخش بعدی، به بررسی ابزارهای نظارت مستمر بر Core Web Vitals خواهیم پرداخت تا بتوانید بهینهسازیهای خود را حفظ و بهبود بخشید.
21. ابزارهای نظارت مستمر بر Core Web Vitals و بهینهسازی مداوم
پس از بهینهسازی اولیه Core Web Vitals، حفظ و بهبود مداوم آنها اهمیت زیادی دارد. با هر بروزرسانی افزونهها، قالبها و محتوا، ممکن است Core Web Vitals دچار تغییر شود. در این بخش، ابزارها و روشهای نظارت مستمر بر Core Web Vitals و بهینهسازی مداوم آنها را بررسی میکنیم.
نظارت مستمر بر Core Web Vitals
1. ابزار نظارت داخلی
PHPfunction core_web_vitals_monitoring_dashboard() {
// ایجاد صفحه داشبورد در بخش مدیریت
add_menu_page(
'Core Web Vitals Monitor',
'CWV Monitor',
'manage_options',
'core-web-vitals-monitor',
'render_cwv_dashboard',
'dashicons-chart-line',
100
);
// اضافه کردن زیرمنوها
add_submenu_page(
'core-web-vitals-monitor',
'Dashboard',
'Dashboard',
'manage_options',
'core-web-vitals-monitor',
'render_cwv_dashboard'
);
add_submenu_page(
'core-web-vitals-monitor',
'Settings',
'Settings',
'manage_options',
'cwv-settings',
'render_cwv_settings'
);
add_submenu_page(
'core-web-vitals-monitor',
'Reports',
'Reports',
'manage_options',
'cwv-reports',
'render_cwv_reports'
);
}
add_action('admin_menu', 'core_web_vitals_monitoring_dashboard');
// ایجاد جدول دیتابیس برای ذخیره دادههای CWV
function create_cwv_database_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'core_web_vitals';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
timestamp datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
page_url varchar(255) NOT NULL,
device varchar(20) NOT NULL,
lcp float,
fid float,
cls float,
ttfb float,
inp float,
lighthouse_score smallint,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_cwv_database_table');
// رندر داشبورد اصلی
function render_cwv_dashboard() {
?>
<div class="wrap">
<h1>Core Web Vitals Monitoring Dashboard</h1>
<div class="cwv-dashboard-header" style="display: flex; justify-content: space-between; margin-bottom: 20px;">
<div>
<h2>Real-time Metrics</h2>
<p>Last updated: <?php echo date('Y-m-d H:i:s'); ?></p>
</div>
<div>
<button id="refresh-cwv-data" class="button button-primary">Refresh Data</button>
<button id="run-lighthouse" class="button">Run Lighthouse Test</button>
</div>
</div>
<div class="cwv-metrics-cards" style="display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 30px;">
<?php
// نمایش کارتهای متریک
$metrics = array(
'lcp' => array('name' => 'LCP', 'good' => 2.5, 'medium' => 4.0, 'unit' => 's'),
'fid' => array('name' => 'FID', 'good' => 100, 'medium' => 300, 'unit' => 'ms'),
'cls' => array('name' => 'CLS', 'good' => 0.1, 'medium' => 0.25, 'unit' => ''),
'ttfb' => array('name' => 'TTFB', 'good' => 800, 'medium' => 1800, 'unit' => 'ms'),
'inp' => array('name' => 'INP', 'good' => 200, 'medium' => 500, 'unit' => 'ms')
);
// دریافت دادههای واقعی از دیتابیس
global $wpdb;
$table_name = $wpdb->prefix . 'core_web_vitals';
foreach ($metrics as $key => $metric) {
// دریافت میانگین متریک از 10 اندازهگیری آخر
$value = $wpdb->get_var(
$wpdb->prepare(
"SELECT AVG($key) FROM (
SELECT $key FROM $table_name
WHERE $key IS NOT NULL
ORDER BY timestamp DESC
LIMIT 10
) as recent_data"
)
);
// اگر دادهای موجود نیست، مقدار پیشفرض
if (is_null($value)) {
$value = 0;
}
// تعیین وضعیت
$status = 'good';
$color = '#0cce6b';
if ($key === 'lcp' || $key === 'ttfb' || $key === 'fid' || $key === 'inp') {
if ($value > $metric['medium']) {
$status = 'poor';
$color = '#ff4e42';
} elseif ($value > $metric['good']) {
$status = 'needs-improvement';
$color = '#ffa400';
}
} else { // CLS
if ($value > $metric['medium']) {
$status = 'poor';
$color = '#ff4e42';
} elseif ($value > $metric['good']) {
$status = 'needs-improvement';
$color = '#ffa400';
}
}
// Format value
$formatted_value = $key === 'cls' ? number_format($value, 3) : number_format($value, 0);
// Display metric card
?>
<div class="cwv-metric-card" style="flex: 1; min-width: 200px; background-color: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0;"><?php echo $metric['name']; ?></h3>
<div class="metric-value" style="font-size: 32px; font-weight: bold; color: <?php echo $color; ?>;">
<?php echo $formatted_value . $metric['unit']; ?>
</div>
<div class="metric-status" style="margin-top: 10px; text-transform: uppercase; font-size: 12px; color: <?php echo $color; ?>;">
<?php echo $status; ?>
</div>
<div class="metric-threshold" style="margin-top: 5px; font-size: 12px; color: #666;">
Good: <?php echo $key === 'cls' ? number_format($metric['good'], 2) : $metric['good']; ?><?php echo $metric['unit']; ?> /
NI: <?php echo $key === 'cls' ? number_format($metric['medium'], 2) : $metric['medium']; ?><?php echo $metric['unit']; ?>
</div>
</div>
<?php
}
// Lighthouse Score Card
$lighthouse_score = $wpdb->get_var(
"SELECT AVG(lighthouse_score) FROM (
SELECT lighthouse_score FROM $table_name
WHERE lighthouse_score IS NOT NULL
ORDER BY timestamp DESC
LIMIT 5
) as recent_scores"
);
if (is_null($lighthouse_score)) {
$lighthouse_score = 0;
}
// تعیین رنگ بر اساس امتیاز
if ($lighthouse_score >= 90) {
$score_color = '#0cce6b';
$score_status = 'GOOD';
} elseif ($lighthouse_score >= 50) {
$score_color = '#ffa400';
$score_status = 'NEEDS IMPROVEMENT';
} else {
$score_color = '#ff4e42';
$score_status = 'POOR';
}
?>
<div class="cwv-metric-card" style="flex: 1; min-width: 200px; background-color: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h3 style="margin-top: 0;">Lighthouse Score</h3>
<div class="metric-value" style="font-size: 32px; font-weight: bold; color: <?php echo $score_color; ?>;">
<?php echo round($lighthouse_score); ?>
</div>
<div class="metric-status" style="margin-top: 10px; text-transform: uppercase; font-size: 12px; color: <?php echo $score_color; ?>;">
<?php echo $score_status; ?>
</div>
<div class="metric-threshold" style="margin-top: 5px; font-size: 12px; color: #666;">
Good: 90+ / NI: 50+
</div>
</div>
</div>
<div class="cwv-charts-container">
<div class="cwv-tabs">
<button class="cwv-tab-button active" data-tab="trends">Trends</button>
<button class="cwv-tab-button" data-tab="pages">Page Analysis</button>
<button class="cwv-tab-button" data-tab="devices">Device Comparison</button>
</div>
<div class="cwv-tab-content" id="trends-tab" style="display: block;">
<h2>Core Web Vitals Trends (Last 30 Days)</h2>
<div style="display: flex; flex-wrap: wrap; gap: 20px;">
<div style="flex: 1; min-width: 400px; height: 300px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<canvas id="lcp-trend-chart"></canvas>
</div>
<div style="flex: 1; min-width: 400px; height: 300px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<canvas id="cls-trend-chart"></canvas>
</div>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px;">
<div style="flex: 1; min-width: 400px; height: 300px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<canvas id="fid-trend-chart"></canvas>
</div>
<div style="flex: 1; min-width: 400px; height: 300px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<canvas id="lighthouse-trend-chart"></canvas>
</div>
</div>
</div>
<div class="cwv-tab-content" id="pages-tab" style="display: none;">
<h2>Page Performance Analysis</h2>
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Page</th>
<th>LCP</th>
<th>FID</th>
<th>CLS</th>
<th>Lighthouse</th>
<th>Last Tested</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
// Get top pages with their average metrics
$pages = $wpdb->get_results(
"SELECT
page_url,
AVG(lcp) as avg_lcp,
AVG(fid) as avg_fid,
AVG(cls) as avg_cls,
AVG(lighthouse_score) as avg_score,
MAX(timestamp) as last_tested
FROM $table_name
GROUP BY page_url
ORDER BY avg_lcp DESC
LIMIT 10"
);
if ($pages) {
foreach ($pages as $page) {
// Determine status colors
$lcp_color = $page->avg_lcp <= 2.5 ? '#0cce6b' : ($page->avg_lcp <= 4 ? '#ffa400' : '#ff4e42');
$fid_color = $page->avg_fid <= 100 ? '#0cce6b' : ($page->avg_fid <= 300 ? '#ffa400' : '#ff4e42');
$cls_color = $page->avg_cls <= 0.1 ? '#0cce6b' : ($page->avg_cls <= 0.25 ? '#ffa400' : '#ff4e42');
$score_color = $page->avg_score >= 90 ? '#0cce6b' : ($page->avg_score >= 50 ? '#ffa400' : '#ff4e42');
// Format page URL for display
$display_url = str_replace(home_url(), '', $page->page_url);
if (empty($display_url)) $display_url = '/';
echo '<tr>';
echo '<td><a href="' . esc_url($page->page_url) . '" target="_blank">' . esc_html($display_url) . '</a></td>';
echo '<td style="color: ' . $lcp_color . ';">' . number_format($page->avg_lcp, 1) . 's</td>';
echo '<td style="color: ' . $fid_color . ';">' . number_format($page->avg_fid, 0) . 'ms</td>';
echo '<td style="color: ' . $cls_color . ';">' . number_format($page->avg_cls, 3) . '</td>';
echo '<td style="color: ' . $score_color . ';">' . number_format($page->avg_score, 0) . '</td>';
echo '<td>' . date('Y-m-d H:i', strtotime($page->last_tested)) . '</td>';
echo '<td><a href="#" class="button button-small test-page" data-url="' . esc_url($page->page_url) . '">Test</a></td>';
echo '</tr>';
}
} else {
echo '<tr><td colspan="7">No data available yet. Run tests to collect data.</td></tr>';
}
?>
</tbody>
</table>
</div>
</div>
<div class="cwv-tab-content" id="devices-tab" style="display: none;">
<h2>Device Comparison</h2>
<div style="display: flex; flex-wrap: wrap; gap: 20px;">
<div style="flex: 1; min-width: 400px; height: 400px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<canvas id="device-comparison-chart"></canvas>
</div>
<div style="flex: 1; min-width: 400px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h3>Device Breakdown</h3>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Device</th>
<th>LCP</th>
<th>FID</th>
<th>CLS</th>
<th>Lighthouse</th>
</tr>
</thead>
<tbody>
<?php
// Get metrics grouped by device
$devices = $wpdb->get_results(
"SELECT
device,
AVG(lcp) as avg_lcp,
AVG(fid) as avg_fid,
AVG(cls) as avg_cls,
AVG(lighthouse_score) as avg_score
FROM $table_name
GROUP BY device"
);
if ($devices) {
foreach ($devices as $device) {
// Determine status colors
$lcp_color = $device->avg_lcp <= 2.5 ? '#0cce6b' : ($device->avg_lcp <= 4 ? '#ffa400' : '#ff4e42');
$fid_color = $device->avg_fid <= 100 ? '#0cce6b' : ($device->avg_fid <= 300 ? '#ffa400' : '#ff4e42');
$cls_color = $device->avg_cls <= 0.1 ? '#0cce6b' : ($device->avg_cls <= 0.25 ? '#ffa400' : '#ff4e42');
$score_color = $device->avg_score >= 90 ? '#0cce6b' : ($device->avg_score >= 50 ? '#ffa400' : '#ff4e42');
echo '<tr>';
echo '<td>' . ucfirst(esc_html($device->device)) . '</td>';
echo '<td style="color: ' . $lcp_color . ';">' . number_format($device->avg_lcp, 1) . 's</td>';
echo '<td style="color: ' . $fid_color . ';">' . number_format($device->avg_fid, 0) . 'ms</td>';
echo '<td style="color: ' . $cls_color . ';">' . number_format($device->avg_cls, 3) . '</td>';
echo '<td style="color: ' . $score_color . ';">' . number_format($device->avg_score, 0) . '</td>';
echo '</tr>';
}
} else {
echo '<tr><td colspan="5">No data available yet. Run tests to collect data.</td></tr>';
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="cwv-recommendations" style="margin-top: 30px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Recommendations</h2>
<ul>
<?php
// Generate recommendations based on metrics
$avg_lcp = $wpdb->get_var("SELECT AVG(lcp) FROM $table_name WHERE lcp IS NOT NULL");
$avg_cls = $wpdb->get_var("SELECT AVG(cls) FROM $table_name WHERE cls IS NOT NULL");
$avg_fid = $wpdb->get_var("SELECT AVG(fid) FROM $table_name WHERE fid IS NOT NULL");
if ($avg_lcp > 2.5) {
echo '<li><strong>LCP Improvement:</strong> ';
if ($avg_lcp > 4) {
echo 'Your LCP is poor. Consider implementing server-side caching, optimizing images, and removing render-blocking resources.';
} else {
echo 'Your LCP needs improvement. Try optimizing images and preloading critical resources.';
}
echo '</li>';
}
if ($avg_cls > 0.1) {
echo '<li><strong>CLS Improvement:</strong> ';
if ($avg_cls > 0.25) {
echo 'Your CLS is poor. Ensure all images have width and height attributes, and reserve space for dynamic content.';
} else {
echo 'Your CLS needs improvement. Check for layout shifts caused by late-loading resources or ads.';
}
echo '</li>';
}
if ($avg_fid > 100) {
echo '<li><strong>FID Improvement:</strong> ';
if ($avg_fid > 300) {
echo 'Your FID is poor. Minimize JavaScript execution time and break up long tasks.';
} else {
echo 'Your FID needs improvement. Consider deferring non-critical JavaScript.';
}
echo '</li>';
}
// If all metrics are good
if ($avg_lcp <= 2.5 && $avg_cls <= 0.1 && $avg_fid <= 100) {
echo '<li><strong>Great job!</strong> Your Core Web Vitals are in good shape. Continue monitoring to maintain performance.</li>';
}
// General recommendations
echo '<li><strong>Regular Monitoring:</strong> Continue monitoring your Core Web Vitals regularly, especially after major updates to your site.</li>';
echo '<li><strong>Mobile Optimization:</strong> Pay special attention to mobile performance, as Google primarily uses mobile metrics for ranking.</li>';
?>
</ul>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
jQuery(document).ready(function($) {
// Tab switching
$('.cwv-tab-button').click(function() {
$('.cwv-tab-button').removeClass('active');
$(this).addClass('active');
const tabId = $(this).data('tab') + '-tab';
$('.cwv-tab-content').hide();
$('#' + tabId).show();
});
// Chart data (would be populated with real data in a full implementation)
const dates = <?php
$dates = $wpdb->get_col(
"SELECT DATE(timestamp) FROM $table_name
GROUP BY DATE(timestamp)
ORDER BY DATE(timestamp) ASC
LIMIT 30"
);
echo json_encode($dates ?: array_map(function($i) { return date('Y-m-d', strtotime("-$i days")); }, range(29, 0)));
?>;
const lcpData = <?php
$lcp_data = $wpdb->get_col(
"SELECT AVG(lcp) FROM $table_name
WHERE lcp IS NOT NULL
GROUP BY DATE(timestamp)
ORDER BY DATE(timestamp) ASC
LIMIT 30"
);
echo json_encode($lcp_data ?: array_fill(0, 30, 0));
?>;
const fidData = <?php
$fid_data = $wpdb->get_col(
"SELECT AVG(fid) FROM $table_name
WHERE fid IS NOT NULL
GROUP BY DATE(timestamp)
ORDER BY DATE(timestamp) ASC
LIMIT 30"
);
echo json_encode($fid_data ?: array_fill(0, 30, 0));
?>;
const clsData = <?php
$cls_data = $wpdb->get_col(
"SELECT AVG(cls) FROM $table_name
WHERE cls IS NOT NULL
GROUP BY DATE(timestamp)
ORDER BY DATE(timestamp) ASC
LIMIT 30"
);
echo json_encode($cls_data ?: array_fill(0, 30, 0));
?>;
const lighthouseData = <?php
$lighthouse_data = $wpdb->get_col(
"SELECT AVG(lighthouse_score) FROM $table_name
WHERE lighthouse_score IS NOT NULL
GROUP BY DATE(timestamp)
ORDER BY DATE(timestamp) ASC
LIMIT 30"
);
echo json_encode($lighthouse_data ?: array_fill(0, 30, 0));
?>;
// LCP Trend Chart
new Chart(document.getElementById('lcp-trend-chart'), {
type: 'line',
data: {
labels: dates,
datasets: [{
label: 'LCP (seconds)',
data: lcpData,
borderColor: '#0073aa',
backgroundColor: 'rgba(0, 115, 170, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'LCP Trend'
}
},
scales: {
y: {
beginAtZero: true,
suggestedMax: 5
}
}
}
});
// CLS Trend Chart
new Chart(document.getElementById('cls-trend-chart'), {
type: 'line',
data: {
labels: dates,
datasets: [{
label: 'CLS',
data: clsData,
borderColor: '#00a0d2',
backgroundColor: 'rgba(0, 160, 210, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'CLS Trend'
}
},
scales: {
y: {
beginAtZero: true,
suggestedMax: 0.5
}
}
}
});
// FID Trend Chart
new Chart(document.getElementById('fid-trend-chart'), {
type: 'line',
data: {
labels: dates,
datasets: [{
label: 'FID (ms)',
data: fidData,
borderColor: '#46b450',
backgroundColor: 'rgba(70, 180, 80, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'FID Trend'
}
},
scales: {
y: {
beginAtZero: true,
suggestedMax: 500
}
}
}
});
// Lighthouse Trend Chart
new Chart(document.getElementById('lighthouse-trend-chart'), {
type: 'line',
data: {
labels: dates,
datasets: [{
label: 'Lighthouse Score',
data: lighthouseData,
borderColor: '#826eb4',
backgroundColor: 'rgba(130, 110, 180, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Lighthouse Score Trend'
}
},
scales: {
y: {
beginAtZero: true,
suggestedMax: 100
}
}
}
});
// Device Comparison Chart
new Chart(document.getElementById('device-comparison-chart'), {
type: 'bar',
data: {
labels: ['LCP (s)', 'FID (ms/100)', 'CLS (x100)', 'Lighthouse Score'],
datasets: [
<?php
$devices = $wpdb->get_results(
"SELECT
device,
AVG(lcp) as avg_lcp,
AVG(fid) as avg_fid,
AVG(cls) as avg_cls,
AVG(lighthouse_score) as avg_score
FROM $table_name
GROUP BY device"
);
$colors = [
'desktop' => 'rgba(0, 115, 170, 0.7)',
'mobile' => 'rgba(70, 180, 80, 0.7)',
'tablet' => 'rgba(130, 110, 180, 0.7)'
];
if ($devices) {
foreach ($devices as $index => $device) {
$color = isset($colors[$device->device]) ? $colors[$device->device] : 'rgba(' . rand(0, 255) . ',' . rand(0, 255) . ',' . rand(0, 255) . ', 0.7)';
echo "{
label: '" . ucfirst($device->device) . "',
data: [" .
$device->avg_lcp . ", " .
($device->avg_fid / 100) . ", " .
($device->avg_cls * 100) . ", " .
$device->avg_score .
"],
backgroundColor: '$color'
}";
if ($index < count($devices) - 1) {
echo ",";
}
}
} else {
// Sample data if no real data exists
echo "{
label: 'Desktop',
data: [2.5, 0.8, 5, 85],
backgroundColor: 'rgba(0, 115, 170, 0.7)'
},
{
label: 'Mobile',
data: [3.2, 1.2, 8, 75],
backgroundColor: 'rgba(70, 180, 80, 0.7)'
}";
}
?>
]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Performance by Device Type'
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
let value = context.raw;
switch(context.dataIndex) {
case 0: return label + ': ' + value.toFixed(1) + 's';
case 1: return label + ': ' + (value * 100).toFixed(0) + 'ms';
case 2: return label + ': ' + (value / 100).toFixed(3);
case 3: return label + ': ' + value.toFixed(0);
}
}
}
}
}
}
});
// Refresh data button
$('#refresh-cwv-data').click(function() {
// In a real implementation, this would trigger an AJAX call to refresh the data
alert('Data refresh functionality would be implemented here.');
});
// Run Lighthouse test button
$('#run-lighthouse').click(function() {
// In a real implementation, this would trigger a Lighthouse test
alert('Lighthouse test functionality would be implemented here.');
});
// Test page buttons
$('.test-page').click(function(e) {
e.preventDefault();
const url = $(this).data('url');
alert('Testing page: ' + url + '\nThis functionality would be implemented in a real version.');
});
});
</script>
<style>
.cwv-tabs {
display: flex;
border-bottom: 1px solid #ccc;
margin-bottom: 20px;
}
.cwv-tab-button {
padding: 10px 20px;
background: none;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
}
.cwv-tab-button.active {
border-bottom: 2px solid #0073aa;
color: #0073aa;
}
.cwv-tab-content {
display: none;
}
</style>
<?php
}
// رندر صفحه تنظیمات
function render_cwv_settings() {
// ذخیره تنظیمات
if (isset($_POST['cwv_settings_submit'])) {
update_option('cwv_monitor_active', isset($_POST['cwv_monitor_active']) ? 1 : 0);
update_option('cwv_monitor_frequency', sanitize_text_field($_POST['cwv_monitor_frequency']));
update_option('cwv_monitor_pages', sanitize_textarea_field($_POST['cwv_monitor_pages']));
update_option('cwv_monitor_email', sanitize_email($_POST['cwv_monitor_email']));
update_option('cwv_threshold_lcp', floatval($_POST['cwv_threshold_lcp']));
update_option('cwv_threshold_fid', floatval($_POST['cwv_threshold_fid']));
update_option('cwv_threshold_cls', floatval($_POST['cwv_threshold_cls']));
echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully!</p></div>';
}
// دریافت تنظیمات
$monitor_active = get_option('cwv_monitor_active', 1);
$monitor_frequency = get_option('cwv_monitor_frequency', 'daily');
$monitor_pages = get_option('cwv_monitor_pages', home_url() . "\n" . home_url('/about/') . "\n" . home_url('/contact/'));
$monitor_email = get_option('cwv_monitor_email', get_option('admin_email'));
$threshold_lcp = get_option('cwv_threshold_lcp', 2.5);
$threshold_fid = get_option('cwv_threshold_fid', 100);
$threshold_cls = get_option('cwv_threshold_cls', 0.1);
?>
<div class="wrap">
<h1>Core Web Vitals Monitor Settings</h1>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">Enable Monitoring</th>
<td>
<label>
<input type="checkbox" name="cwv_monitor_active" value="1" <?php checked($monitor_active, 1); ?>>
Activate automatic Core Web Vitals monitoring
</label>
</td>
</tr>
<tr>
<th scope="row">Monitoring Frequency</th>
<td>
<select name="cwv_monitor_frequency">
<option value="hourly" <?php selected($monitor_frequency, 'hourly'); ?>>Hourly</option>
<option value="daily" <?php selected($monitor_frequency, 'daily'); ?>>Daily</option>
<option value="weekly" <?php selected($monitor_frequency, 'weekly'); ?>>Weekly</option>
</select>
</td>
</tr>
<tr>
<th scope="row">Pages to Monitor</th>
<td>
<textarea name="cwv_monitor_pages" rows="5" cols="50" class="large-text"><?php echo esc_textarea($monitor_pages); ?></textarea>
<p class="description">Enter one URL per line. These pages will be monitored for Core Web Vitals.</p>
</td>
</tr>
<tr>
<th scope="row">Email Notifications</th>
<td>
<input type="email" name="cwv_monitor_email" value="<?php echo esc_attr($monitor_email); ?>" class="regular-text">
<p class="description">Email address to receive alerts when metrics fall below thresholds.</p>
</td>
</tr>
<tr>
<th scope="row" colspan="2"><h3>Alert Thresholds</h3></th>
</tr>
<tr>
<th scope="row">LCP Threshold (seconds)</th>
<td>
<input type="number" name="cwv_threshold_lcp" value="<?php echo esc_attr($threshold_lcp); ?>" step="0.1" min="0.1" class="small-text">
<p class="description">You'll receive alerts when LCP exceeds this value. Google recommends keeping LCP under 2.5 seconds.</p>
</td>
</tr>
<tr>
<th scope="row">FID Threshold (milliseconds)</th>
<td>
<input type="number" name="cwv_threshold_fid" value="<?php echo esc_attr($threshold_fid); ?>" step="1" min="1" class="small-text">
<p class="description">You'll receive alerts when FID exceeds this value. Google recommends keeping FID under 100 milliseconds.</p>
</td>
</tr>
<tr>
<th scope="row">CLS Threshold</th>
<td>
<input type="number" name="cwv_threshold_cls" value="<?php echo esc_attr($threshold_cls); ?>" step="0.01" min="0.01" max="1" class="small-text">
<p class="description">You'll receive alerts when CLS exceeds this value. Google recommends keeping CLS under 0.1.</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="cwv_settings_submit" class="button button-primary" value="Save Settings">
</p>
</form>
</div>
<?php
}
// رندر صفحه گزارشها
function render_cwv_reports() {
global $wpdb;
$table_name = $wpdb->prefix . 'core_web_vitals';
// پردازش فیلترها
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : date('Y-m-d', strtotime('-30 days'));
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
$device = isset($_GET['device']) ? sanitize_text_field($_GET['device']) : '';
$page = isset($_GET['page_url']) ? sanitize_text_field($_GET['page_url']) : '';
// ساخت کوئری
$query = "SELECT * FROM $table_name WHERE 1=1";
$args = array();
if ($date_from) {
$query .= " AND DATE(timestamp) >= %s";
$args[] = $date_from;
}
if ($date_to) {
$query .= " AND DATE(timestamp) <= %s";
$args[] = $date_to;
}
if ($device) {
$query .= " AND device = %s";
$args[] = $device;
}
if ($page) {
$query .= " AND page_url LIKE %s";
$args[] = '%' . $wpdb->esc_like($page) . '%';
}
$query .= " ORDER BY timestamp DESC";
// اجرای کوئری
$results = $wpdb->get_results($wpdb->prepare($query, $args));
?>
<div class="wrap">
<h1>Core Web Vitals Reports</h1>
<div class="tablenav top">
<form method="get">
<input type="hidden" name="page" value="cwv-reports">
<div class="alignleft actions">
<input type="date" name="date_from" value="<?php echo esc_attr($date_from); ?>">
<span>to</span>
<input type="date" name="date_to" value="<?php echo esc_attr($date_to); ?>">
<select name="device">
<option value="">All Devices</option>
<option value="desktop" <?php selected($device, 'desktop'); ?>>Desktop</option>
<option value="mobile" <?php selected($device, 'mobile'); ?>>Mobile</option>
<option value="tablet" <?php selected($device, 'tablet'); ?>>Tablet</option>
</select>
<input type="text" name="page_url" placeholder="Filter by URL" value="<?php echo esc_attr($page); ?>">
<input type="submit" class="button" value="Filter">
</div>
</form>
<div class="alignright">
<a href="<?php echo admin_url('admin.php?page=cwv-reports&export=csv'); ?>" class="button">Export to CSV</a>
</div>
<br class="clear">
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Date & Time</th>
<th>Page URL</th>
<th>Device</th>
<th>LCP</th>
<th>FID</th>
<th>CLS</th>
<th>TTFB</th>
<th>INP</th>
<th>Lighthouse</th>
</tr>
</thead>
<tbody>
<?php
if ($results) {
foreach ($results as $result) {
// Determine status colors
$lcp_color = $result->lcp <= 2.5 ? '#0cce6b' : ($result->lcp <= 4 ? '#ffa400' : '#ff4e42');
$fid_color = $result->fid <= 100 ? '#0cce6b' : ($result->fid <= 300 ? '#ffa400' : '#ff4e42');
$cls_color = $result->cls <= 0.1 ? '#0cce6b' : ($result->cls <= 0.25 ? '#ffa400' : '#ff4e42');
$ttfb_color = $result->ttfb <= 800 ? '#0cce6b' : ($result->ttfb <= 1800 ? '#ffa400' : '#ff4e42');
$inp_color = $result->inp <= 200 ? '#0cce6b' : ($result->inp <= 500 ? '#ffa400' : '#ff4e42');
$score_color = $result->lighthouse_score >= 90 ? '#0cce6b' : ($result->lighthouse_score >= 50 ? '#ffa400' : '#ff4e42');
// Format page URL for display
$display_url = str_replace(home_url(), '', $result->page_url);
if (empty($display_url)) $display_url = '/';
echo '<tr>';
echo '<td>' . date('Y-m-d H:i', strtotime($result->timestamp)) . '</td>';
echo '<td><a href="' . esc_url($result->page_url) . '" target="_blank">' . esc_html($display_url) . '</a></td>';
echo '<td>' . ucfirst(esc_html($result->device)) . '</td>';
echo '<td style="color: ' . $lcp_color . ';">' . ($result->lcp ? number_format($result->lcp, 1) . 's' : 'N/A') . '</td>';
echo '<td style="color: ' . $fid_color . ';">' . ($result->fid ? number_format($result->fid, 0) . 'ms' : 'N/A') . '</td>';
echo '<td style="color: ' . $cls_color . ';">' . ($result->cls ? number_format($result->cls, 3) : 'N/A') . '</td>';
echo '<td style="color: ' . $ttfb_color . ';">' . ($result->ttfb ? number_format($result->ttfb, 0) . 'ms' : 'N/A') . '</td>';
echo '<td style="color: ' . $inp_color . ';">' . ($result->inp ? number_format($result->inp, 0) . 'ms' : 'N/A') . '</td>';
echo '<td style="color: ' . $score_color . ';">' . ($result->lighthouse_score ? number_format($result->lighthouse_score, 0) : 'N/A') . '</td>';
echo '</tr>';
}
} else {
echo '<tr><td colspan="9">No data available for the selected filters.</td></tr>';
}
?>
</tbody>
</table>
</div>
<?php
}
// زمانبندی اندازهگیری خودکار Core Web Vitals
function schedule_cwv_measurements() {
// بررسی فعال بودن نظارت
$monitor_active = get_option('cwv_monitor_active', 1);
if (!$monitor_active) {
return;
}
// دریافت تنظیمات
$frequency = get_option('cwv_monitor_frequency', 'daily');
$pages = explode("\n", get_option('cwv_monitor_pages', home_url()));
$pages = array_map('trim', $pages);
$pages = array_filter($pages);
// اگر صفحهای تعریف نشده، صفحه اصلی را اضافه کن
if (empty($pages)) {
$pages = array(home_url());
}
// دریافت متریکها برای هر صفحه
foreach ($pages as $page_url) {
measure_core_web_vitals($page_url);
}
}
// زمانبندی کرون جاب
if (!wp_next_scheduled('cwv_measurement_cron')) {
wp_schedule_event(time(), 'daily', 'cwv_measurement_cron');
}
add_action('cwv_measurement_cron', 'schedule_cwv_measurements');
// تابع اندازهگیری Core Web Vitals
function measure_core_web_vitals($page_url) {
// در یک پیادهسازی واقعی، این تابع میتواند از API های مختلف برای اندازهگیری CWV استفاده کند
// مثلاً PageSpeed Insights API یا Chrome UX Report API
// برای مثال، دادههای تصادفی تولید میکنیم
$devices = array('desktop', 'mobile');
$device = $devices[array_rand($devices)];
$lcp = mt_rand(15, 50) / 10; // 1.5s to 5.0s
$fid = mt_rand(50, 400); // 50ms to 400ms
$cls = mt_rand(1, 40) / 100; // 0.01 to 0.40
$ttfb = mt_rand(200, 2000); // 200ms to 2000ms
$inp = mt_rand(100, 700); // 100ms to 700ms
$lighthouse_score = mt_rand(40, 100); // 40 to 100
// ذخیره در دیتابیس
global $wpdb;
$table_name = $wpdb->prefix . 'core_web_vitals';
$wpdb->insert(
$table_name,
array(
'timestamp' => current_time('mysql'),
'page_url' => $page_url,
'device' => $device,
'lcp' => $lcp,
'fid' => $fid,
'cls' => $cls,
'ttfb' => $ttfb,
'inp' => $inp,
'lighthouse_score' => $lighthouse_score
),
array('%s', '%s', '%s', '%f', '%f', '%f', '%f', '%f', '%d')
);
// بررسی آستانهها و ارسال هشدار در صورت نیاز
check_cwv_thresholds($page_url, $device, $lcp, $fid, $cls);
return true;
}
// بررسی آستانهها و ارسال هشدار در صورت نیاز
function check_cwv_thresholds($page_url, $device, $lcp, $fid, $cls) {
// دریافت آستانهها
$threshold_lcp = get_option('cwv_threshold_lcp', 2.5);
$threshold_fid = get_option('cwv_threshold_fid', 100);
$threshold_cls = get_option('cwv_threshold_cls', 0.1);
// بررسی تجاوز از آستانهها
$issues = array();
if ($lcp > $threshold_lcp) {
$issues[] = "LCP is {$lcp}s (threshold: {$threshold_lcp}s)";
}
if ($fid > $threshold_fid) {
$issues[] = "FID is {$fid}ms (threshold: {$threshold_fid}ms)";
}
if ($cls > $threshold_cls) {
$issues[] = "CLS is {$cls} (threshold: {$threshold_cls})";
}
// ارسال ایمیل هشدار اگر مشکلی وجود داشته باشد
if (!empty($issues)) {
$email = get_option('cwv_monitor_email', get_option('admin_email'));
$subject = 'Core Web Vitals Alert: ' . parse_url($page_url, PHP_URL_HOST);
$message = "Core Web Vitals issues detected on {$page_url} ({$device}):\n\n";
$message .= implode("\n", $issues);
$message .= "\n\nPlease check your Core Web Vitals Monitor dashboard for more details.";
wp_mail($email, $subject, $message);
}
}
// اضافه کردن ویجت به داشبورد وردپرس
function add_cwv_dashboard_widget() {
wp_add_dashboard_widget(
'cwv_dashboard_widget',
'Core Web Vitals Summary',
'render_cwv_dashboard_widget'
);
}
add_action('wp_dashboard_setup', 'add_cwv_dashboard_widget');
// رندر ویجت داشبورد
function render_cwv_dashboard_widget() {
global $wpdb;
$table_name = $wpdb->prefix . 'core_web_vitals';
// دریافت میانگین متریکها
$avg_lcp = $wpdb->get_var("SELECT AVG(lcp) FROM $table_name WHERE lcp IS NOT NULL");
$avg_fid = $wpdb->get_var("SELECT AVG(fid) FROM $table_name WHERE fid IS NOT NULL");
$avg_cls = $wpdb->get_var("SELECT AVG(cls) FROM $table_name WHERE cls IS NOT NULL");
$avg_score = $wpdb->get_var("SELECT AVG(lighthouse_score) FROM $table_name WHERE lighthouse_score IS NOT NULL");
// تعیین وضعیت
$lcp_status = $avg_lcp <= 2.5 ? 'good' : ($avg_lcp <= 4 ? 'needs-improvement' : 'poor');
$fid_status = $avg_fid <= 100 ? 'good' : ($avg_fid <= 300 ? 'needs-improvement' : 'poor');
$cls_status = $avg_cls <= 0.1 ? 'good' : ($avg_cls <= 0.25 ? 'needs-improvement' : 'poor');
$score_status = $avg_score >= 90 ? 'good' : ($avg_score >= 50 ? 'needs-improvement' : 'poor');
// تعیین رنگها
$status_colors = array(
'good' => '#0cce6b',
'needs-improvement' => '#ffa400',
'poor' => '#ff4e42'
);
?>
<style>
.cwv-widget-metrics {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.cwv-widget-metric {
text-align: center;
flex: 1;
}
.cwv-widget-value {
font-size: 24px;
font-weight: bold;
}
.cwv-widget-status {
font-size: 12px;
text-transform: uppercase;
}
.cwv-widget-good { color: #0cce6b; }
.cwv-widget-needs-improvement { color: #ffa400; }
.cwv-widget-poor { color: #ff4e42; }
</style>
<div class="cwv-widget-metrics">
<div class="cwv-widget-metric">
<div class="cwv-widget-label">LCP</div>
<div class="cwv-widget-value cwv-widget-<?php echo $lcp_status; ?>">
<?php echo $avg_lcp ? number_format($avg_lcp, 1) . 's' : 'N/A'; ?>
</div>
<div class="cwv-widget-status cwv-widget-<?php echo $lcp_status; ?>">
<?php echo str_replace('-', ' ', $lcp_status); ?>
</div>
</div>
<div class="cwv-widget-metric">
<div class="cwv-widget-label">FID</div>
<div class="cwv-widget-value cwv-widget-<?php echo $fid_status; ?>">
<?php echo $avg_fid ? number_format($avg_fid, 0) . 'ms' : 'N/A'; ?>
</div>
<div class="cwv-widget-status cwv-widget-<?php echo $fid_status; ?>">
<?php echo str_replace('-', ' ', $fid_status); ?>
</div>
</div>
<div class="cwv-widget-metric">
<div class="cwv-widget-label">CLS</div>
<div class="cwv-widget-value cwv-widget-<?php echo $cls_status; ?>">
<?php echo $avg_cls ? number_format($avg_cls, 3) : 'N/A'; ?>
</div>
<div class="cwv-widget-status cwv-widget-<?php echo $cls_status; ?>">
<?php echo str_replace('-', ' ', $cls_status); ?>
</div>
</div>
<div class="cwv-widget-metric">
<div class="cwv-widget-label">Lighthouse</div>
<div class="cwv-widget-value cwv-widget-<?php echo $score_status; ?>">
<?php echo $avg_score ? number_format($avg_score, 0) : 'N/A'; ?>
</div>
<div class="cwv-widget-status cwv-widget-<?php echo $score_status; ?>">
<?php echo str_replace('-', ' ', $score_status); ?>
</div>
</div>
</div>
<p>
<a href="<?php echo admin_url('admin.php?page=core-web-vitals-monitor'); ?>" class="button button-primary">View Full Dashboard</a>
<a href="<?php echo admin_url('admin.php?page=cwv-reports'); ?>" class="button">View Reports</a>
</p>
<?php
}
2. استفاده از Google Search Console
PHPfunction google_search_console_cwv_integration() {
if (is_admin() && current_user_can('manage_options')) {
add_submenu_page(
'core-web-vitals-monitor',
'Search Console Integration',
'Search Console',
'manage_options',
'cwv-search-console',
'render_search_console_integration'
);
}
}
add_action('admin_menu', 'google_search_console_cwv_integration', 20);
// رندر صفحه یکپارچهسازی با Search Console
function render_search_console_integration() {
// ذخیره تنظیمات
if (isset($_POST['gsc_settings_submit'])) {
update_option('gsc_api_key', sanitize_text_field($_POST['gsc_api_key']));
update_option('gsc_client_id', sanitize_text_field($_POST['gsc_client_id']));
update_option('gsc_client_secret', sanitize_text_field($_POST['gsc_client_secret']));
echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully!</p></div>';
}
// دریافت تنظیمات
$api_key = get_option('gsc_api_key', '');
$client_id = get_option('gsc_client_id', '');
$client_secret = get_option('gsc_client_secret', '');
// بررسی اتصال
$is_connected = !empty($api_key) && !empty($client_id) && !empty($client_secret);
?>
<div class="wrap">
<h1>Google Search Console Integration</h1>
<div class="notice notice-info">
<p>Integrate with Google Search Console to import real Core Web Vitals data from actual users of your site.</p>
</div>
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px;">
<h2>Connection Settings</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">API Key</th>
<td>
<input type="text" name="gsc_api_key" value="<?php echo esc_attr($api_key); ?>" class="regular-text">
<p class="description">Enter your Google API Key with Search Console API access.</p>
</td>
</tr>
<tr>
<th scope="row">Client ID</th>
<td>
<input type="text" name="gsc_client_id" value="<?php echo esc_attr($client_id); ?>" class="regular-text">
<p class="description">Enter your Google OAuth Client ID.</p>
</td>
</tr>
<tr>
<th scope="row">Client Secret</th>
<td>
<input type="password" name="gsc_client_secret" value="<?php echo esc_attr($client_secret); ?>" class="regular-text">
<p class="description">Enter your Google OAuth Client Secret.</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="gsc_settings_submit" class="button button-primary" value="Save Settings">
<?php if ($is_connected): ?>
<a href="<?php echo admin_url('admin.php?page=cwv-search-console&action=test_connection'); ?>" class="button">Test Connection</a>
<?php endif; ?>
</p>
</form>
</div>
<?php if ($is_connected): ?>
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Search Console Core Web Vitals Data</h2>
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>Last 28 Days Summary</h3>
<div style="display: flex; justify-content: space-between; margin-top: 15px;">
<div style="text-align: center;">
<div style="font-size: 18px; font-weight: bold; color: #0cce6b;">89%</div>
<div>Good URLs</div>
</div>
<div style="text-align: center;">
<div style="font-size: 18px; font-weight: bold; color: #ffa400;">7%</div>
<div>Needs Improvement</div>
</div>
<div style="text-align: center;">
<div style="font-size: 18px; font-weight: bold; color: #ff4e42;">4%</div>
<div>Poor URLs</div>
</div>
</div>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>Field Data vs Lab Data</h3>
<div style="display: flex; justify-content: space-between; margin-top: 15px;">
<div style="text-align: center;">
<div style="font-size: 14px; font-weight: bold;">Field LCP</div>
<div style="font-size: 18px; color: #0cce6b;">2.3s</div>
</div>
<div style="text-align: center;">
<div style="font-size: 14px; font-weight: bold;">Lab LCP</div>
<div style="font-size: 18px; color: #0cce6b;">1.9s</div>
</div>
<div style="text-align: center;">
<div style="font-size: 14px; font-weight: bold;">Field CLS</div>
<div style="font-size: 18px; color: #0cce6b;">0.08</div>
</div>
<div style="text-align: center;">
<div style="font-size: 14px; font-weight: bold;">Lab CLS</div>
<div style="font-size: 18px; color: #ffa400;">0.12</div>
</div>
</div>
</div>
</div>
<h3>URLs with Core Web Vitals Issues</h3>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>URL</th>
<th>Status</th>
<th>LCP</th>
<th>FID</th>
<th>CLS</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="<?php echo home_url('/sample-page/'); ?>" target="_blank">/sample-page/</a></td>
<td><span style="color: #ff4e42;">Poor</span></td>
<td>4.8s</td>
<td>95ms</td>
<td>0.32</td>
<td><a href="#" class="button button-small">Analyze</a></td>
</tr>
<tr>
<td><a href="<?php echo home_url('/about/'); ?>" target="_blank">/about/</a></td>
<td><span style="color: #ffa400;">Needs Improvement</span></td>
<td>3.2s</td>
<td>85ms</td>
<td>0.15</td>
<td><a href="#" class="button button-small">Analyze</a></td>
</tr>
<tr>
<td><a href="<?php echo home_url('/contact/'); ?>" target="_blank">/contact/</a></td>
<td><span style="color: #ffa400;">Needs Improvement</span></td>
<td>2.7s</td>
<td>120ms</td>
<td>0.09</td>
<td><a href="#" class="button button-small">Analyze</a></td>
</tr>
</tbody>
</table>
<p class="description" style="margin-top: 10px;">Note: This is sample data. Connect to your Search Console account to see actual data.</p>
<div style="margin-top: 20px;">
<a href="#" class="button button-primary">Import Latest Data from Search Console</a>
<a href="#" class="button">Schedule Weekly Import</a>
</div>
</div>
<?php else: ?>
<div class="notice notice-warning">
<p>Please enter your Google API credentials to connect to Search Console.</p>
</div>
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>How to Get Google API Credentials</h2>
<ol>
<li>Go to the <a href="https://console.developers.google.com/" target="_blank">Google API Console</a></li>
<li>Create a new project or select an existing one</li>
<li>Enable the "Search Console API"</li>
<li>Go to "Credentials" and create an OAuth client ID</li>
<li>Set the redirect URI to: <code><?php echo admin_url('admin.php?page=cwv-search-console&action=oauth_callback'); ?></code></li>
<li>Copy the Client ID and Client Secret to the form above</li>
<li>Create an API key and copy it to the form above</li>
</ol>
</div>
<?php endif; ?>
</div>
<?php
}
3. یکپارچهسازی با PageSpeed Insights API
PHPfunction pagespeed_insights_integration() {
if (is_admin() && current_user_can('manage_options')) {
add_submenu_page(
'core-web-vitals-monitor',
'PageSpeed Insights',
'PageSpeed Insights',
'manage_options',
'cwv-pagespeed',
'render_pagespeed_integration'
);
}
}
add_action('admin_menu', 'pagespeed_insights_integration', 30);
// رندر صفحه یکپارچهسازی با PageSpeed Insights
function render_pagespeed_integration() {
// ذخیره تنظیمات
if (isset($_POST['psi_settings_submit'])) {
update_option('psi_api_key', sanitize_text_field($_POST['psi_api_key']));
echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully!</p></div>';
}
// اجرای تست
if (isset($_POST['run_psi_test'])) {
$url = esc_url_raw($_POST['test_url']);
$strategy = sanitize_text_field($_POST['test_strategy']);
// در یک پیادهسازی واقعی، اینجا فراخوانی API انجام میشود
echo '<div class="notice notice-info is-dismissible"><p>Running PageSpeed Insights test for ' . $url . ' on ' . $strategy . '...</p></div>';
}
// دریافت تنظیمات
$api_key = get_option('psi_api_key', '');
?>
<div class="wrap">
<h1>PageSpeed Insights Integration</h1>
<div class="notice notice-info">
<p>Run PageSpeed Insights tests on your pages and import the results to your Core Web Vitals dashboard.</p>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 1; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>API Settings</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">PageSpeed Insights API Key</th>
<td>
<input type="text" name="psi_api_key" value="<?php echo esc_attr($api_key); ?>" class="regular-text">
<p class="description">Enter your Google API Key with PageSpeed Insights API access.</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="psi_settings_submit" class="button button-primary" value="Save API Key">
</p>
</form>
<hr>
<h2>Run PageSpeed Test</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">URL to Test</th>
<td>
<input type="url" name="test_url" value="<?php echo home_url(); ?>" required class="regular-text">
</td>
</tr>
<tr>
<th scope="row">Device</th>
<td>
<select name="test_strategy">
<option value="mobile">Mobile</option>
<option value="desktop">Desktop</option>
</select>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="run_psi_test" class="button button-primary" value="Run Test">
</p>
</form>
</div>
<div style="flex: 1; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Recent Tests</h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Date</th>
<th>URL</th>
<th>Device</th>
<th>Score</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><?php echo date('Y-m-d H:i'); ?></td>
<td><?php echo home_url(); ?></td>
<td>Mobile</td>
<td>87</td>
<td><a href="#" class="button button-small">View</a></td>
</tr>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime('-1 day')); ?></td>
<td><?php echo home_url('/about/'); ?></td>
<td>Desktop</td>
<td>92</td>
<td><a href="#" class="button button-small">View</a></td>
</tr>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime('-2 day')); ?></td>
<td><?php echo home_url('/contact/'); ?></td>
<td>Mobile</td>
<td>76</td>
<td><a href="#" class="button button-small">View</a></td>
</tr>
</tbody>
</table>
<p class="description" style="margin-top: 10px;">Note: This is sample data. Run actual tests to see your results.</p>
</div>
</div>
<div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Schedule Automated Tests</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">Enable Scheduled Tests</th>
<td>
<label>
<input type="checkbox" name="psi_enable_schedule" value="1" checked>
Automatically run PageSpeed Insights tests on your important pages
</label>
</td>
</tr>
<tr>
<th scope="row">Frequency</th>
<td>
<select name="psi_schedule_frequency">
<option value="daily">Daily</option>
<option value="weekly" selected>Weekly</option>
<option value="monthly">Monthly</option>
</select>
</td>
</tr>
<tr>
<th scope="row">Pages to Test</th>
<td>
<textarea name="psi_schedule_pages" rows="5" cols="50" class="large-text"><?php echo home_url() . "\n" . home_url('/about/') . "\n" . home_url('/contact/'); ?></textarea>
<p class="description">Enter one URL per line. These pages will be tested automatically.</p>
</td>
</tr>
<tr>
<th scope="row">Devices</th>
<td>
<label><input type="checkbox" name="psi_schedule_devices[]" value="mobile" checked> Mobile</label><br>
<label><input type="checkbox" name="psi_schedule_devices[]" value="desktop" checked> Desktop</label>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="psi_schedule_submit" class="button button-primary" value="Save Schedule">
</p>
</form>
</div>
</div>
<?php
}
بهینهسازی مداوم Core Web Vitals
1. ابزار بهینهسازی خودکار تصاویر
PHPfunction auto_image_optimization_tool() {
if (is_admin() && current_user_can('manage_options')) {
add_submenu_page(
'core-web-vitals-monitor',
'Image Optimizer',
'Image Optimizer',
'manage_options',
'cwv-image-optimizer',
'render_image_optimizer'
);
}
}
add_action('admin_menu', 'auto_image_optimization_tool', 40);
// رندر صفحه بهینهسازی تصاویر
function render_image_optimizer() {
// پردازش بهینهسازی تصاویر
if (isset($_POST['optimize_images'])) {
$optimize_method = sanitize_text_field($_POST['optimize_method']);
$convert_webp = isset($_POST['convert_webp']) ? true : false;
$resize_large = isset($_POST['resize_large']) ? true : false;
$add_dimensions = isset($_POST['add_dimensions']) ? true : false;
// در یک پیادهسازی واقعی، اینجا فرآیند بهینهسازی انجام میشود
echo '<div class="notice notice-success is-dismissible"><p>Image optimization started with method: ' . $optimize_method . '</p></div>';
}
// پردازش تنظیمات
if (isset($_POST['save_image_settings'])) {
update_option('cwv_auto_optimize_images', isset($_POST['auto_optimize']) ? 1 : 0);
update_option('cwv_image_quality', intval($_POST['image_quality']));
update_option('cwv_max_image_width', intval($_POST['max_image_width']));
update_option('cwv_default_image_format', sanitize_text_field($_POST['default_format']));
echo '<div class="notice notice-success is-dismissible"><p>Image optimization settings saved!</p></div>';
}
// دریافت تنظیمات
$auto_optimize = get_option('cwv_auto_optimize_images', 0);
$image_quality = get_option('cwv_image_quality', 85);
$max_width = get_option('cwv_max_image_width', 1920);
$default_format = get_option('cwv_default_image_format', 'webp');
// دریافت آمار تصاویر
$attachment_count = wp_count_attachments();
$image_count = 0;
foreach (array('image/jpeg', 'image/png', 'image/gif') as $mime) {
if (isset($attachment_count->$mime)) {
$image_count += $attachment_count->$mime;
}
}
// تخمین فضای قابل ذخیره
$savings_estimate = $image_count * 0.5; // تخمین 50% کاهش اندازه برای هر تصویر
?>
<div class="wrap">
<h1>Core Web Vitals Image Optimizer</h1>
<div class="notice notice-info">
<p>Optimize your images to improve LCP and CLS metrics and reduce page weight.</p>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 2; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Optimize Images</h2>
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center;">
<div style="font-size: 36px; font-weight: bold; color: #0073aa;"><?php echo number_format($image_count); ?></div>
<div>Total Images</div>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center;">
<div style="font-size: 36px; font-weight: bold; color: #46b450;"><?php echo number_format($savings_estimate, 1); ?> MB</div>
<div>Estimated Savings</div>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center;">
<div style="font-size: 36px; font-weight: bold; color: #0073aa;">0%</div>
<div>Optimized</div>
</div>
</div>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">Optimization Method</th>
<td>
<select name="optimize_method">
<option value="lossless">Lossless (No quality loss)</option>
<option value="lossy" selected>Lossy (Better compression)</option>
<option value="aggressive">Aggressive (Maximum compression)</option>
</select>
</td>
</tr>
<tr>
<th scope="row">Options</th>
<td>
<label><input type="checkbox" name="convert_webp" checked> Convert images to WebP format</label><br>
<label><input type="checkbox" name="resize_large" checked> Resize oversized images</label><br>
<label><input type="checkbox" name="add_dimensions" checked> Add width/height attributes to prevent CLS</label>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="optimize_images" class="button button-primary" value="Start Optimization">
</p>
</form>
<div class="progress-bar-container" style="height: 25px; background: #f1f1f1; border-radius: 4px; margin-top: 20px; display: none;">
<div class="progress-bar" style="width: 0%; height: 100%; background: #0073aa; border-radius: 4px; transition: width 0.3s;"></div>
</div>
<div class="progress-text" style="text-align: center; margin-top: 5px; display: none;">0% Complete</div>
</div>
<div style="flex: 1; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Settings</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">Auto-Optimization</th>
<td>
<label>
<input type="checkbox" name="auto_optimize" value="1" <?php checked($auto_optimize, 1); ?>>
Automatically optimize images on upload
</label>
</td>
</tr>
<tr>
<th scope="row">Image Quality</th>
<td>
<input type="range" name="image_quality" min="60" max="100" value="<?php echo esc_attr($image_quality); ?>" oninput="this.nextElementSibling.value = this.value">
<output><?php echo esc_html($image_quality); ?></output>
<p class="description">Lower values = smaller files but lower quality</p>
</td>
</tr>
<tr>
<th scope="row">Maximum Width</th>
<td>
<input type="number" name="max_image_width" value="<?php echo esc_attr($max_width); ?>" class="small-text"> px
<p class="description">Resize large images to this maximum width</p>
</td>
</tr>
<tr>
<th scope="row">Default Format</th>
<td>
<select name="default_format">
<option value="original" <?php selected($default_format, 'original'); ?>>Keep original</option>
<option value="webp" <?php selected($default_format, 'webp'); ?>>WebP</option>
<option value="avif" <?php selected($default_format, 'avif'); ?>>AVIF</option>
</select>
<p class="description">Format to convert images to</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="save_image_settings" class="button button-primary" value="Save Settings">
</p>
</form>
</div>
</div>
<div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Recently Optimized Images</h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Image</th>
<th>File</th>
<th>Original Size</th>
<th>Optimized Size</th>
<th>Savings</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="<?php echo admin_url('images/w-logo-blue.png'); ?>" width="60" height="60" alt="Sample"></td>
<td>sample-image-1.jpg</td>
<td>1.2 MB</td>
<td>320 KB</td>
<td>73%</td>
<td><?php echo date('Y-m-d H:i'); ?></td>
</tr>
<tr>
<td><img src="<?php echo admin_url('images/w-logo-blue.png'); ?>" width="60" height="60" alt="Sample"></td>
<td>sample-image-2.png</td>
<td>2.8 MB</td>
<td>640 KB</td>
<td>77%</td>
<td><?php echo date('Y-m-d H:i', strtotime('-1 hour')); ?></td>
</tr>
<tr>
<td><img src="<?php echo admin_url('images/w-logo-blue.png'); ?>" width="60" height="60" alt="Sample"></td>
<td>sample-image-3.jpg</td>
<td>950 KB</td>
<td>280 KB</td>
<td>71%</td>
<td><?php echo date('Y-m-d H:i', strtotime('-2 hour')); ?></td>
</tr>
</tbody>
</table>
<p class="description" style="margin-top: 10px;">Note: This is sample data. Optimize images to see your actual results.</p>
</div>
<div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Image Optimization Tips for Core Web Vitals</h2>
<div style="display: flex; gap: 20px; margin-top: 15px;">
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>LCP Improvement</h3>
<ul>
<li>Use modern formats like WebP and AVIF</li>
<li>Implement responsive images with srcset</li>
<li>Preload hero images</li>
<li>Use a CDN for faster delivery</li>
<li>Compress images without significant quality loss</li>
</ul>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>CLS Prevention</h3>
<ul>
<li>Always specify width and height attributes</li>
<li>Use aspect-ratio CSS property</li>
<li>Maintain consistent image dimensions</li>
<li>Pre-allocate space for images</li>
<li>Use modern lazy-loading techniques</li>
</ul>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>Best Practices</h3>
<ul>
<li>Optimize images before uploading</li>
<li>Use appropriate dimensions for each device</li>
<li>Remove unnecessary metadata</li>
<li>Choose the right format for each image type</li>
<li>Regularly audit and re-optimize images</li>
</ul>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('form[name="optimize_images"]').on('submit', function(e) {
// نمایش پیشرفت
$('.progress-bar-container, .progress-text').show();
// شبیهسازی پیشرفت
let progress = 0;
const interval = setInterval(function() {
progress += Math.random() * 10;
if (progress > 100) progress = 100;
$('.progress-bar').css('width', progress + '%');
$('.progress-text').text(Math.round(progress) + '% Complete');
if (progress === 100) {
clearInterval(interval);
setTimeout(function() {
alert('Image optimization completed!');
}, 500);
}
}, 500);
// حذف این خط در پیادهسازی واقعی
// e.preventDefault();
});
});
</script>
<?php
}
2. ابزار بهینهسازی خودکار JavaScript و CSS
PHPfunction auto_js_css_optimization_tool() {
if (is_admin() && current_user_can('manage_options')) {
add_submenu_page(
'core-web-vitals-monitor',
'JS & CSS Optimizer',
'JS & CSS Optimizer',
'manage_options',
'cwv-js-css-optimizer',
'render_js_css_optimizer'
);
}
}
add_action('admin_menu', 'auto_js_css_optimization_tool', 50);
// رندر صفحه بهینهسازی JavaScript و CSS
function render_js_css_optimizer() {
// پردازش بهینهسازی
if (isset($_POST['optimize_js_css'])) {
$minify_js = isset($_POST['minify_js']) ? true : false;
$minify_css = isset($_POST['minify_css']) ? true : false;
$combine_js = isset($_POST['combine_js']) ? true : false;
$combine_css = isset($_POST['combine_css']) ? true : false;
$defer_js = isset($_POST['defer_js']) ? true : false;
$critical_css = isset($_POST['critical_css']) ? true : false;
// در یک پیادهسازی واقعی، اینجا فرآیند بهینهسازی انجام میشود
echo '<div class="notice notice-success is-dismissible"><p>JavaScript and CSS optimization started!</p></div>';
}
// پردازش تنظیمات
if (isset($_POST['save_js_css_settings'])) {
update_option('cwv_auto_optimize_js_css', isset($_POST['auto_optimize_js_css']) ? 1 : 0);
update_option('cwv_excluded_js', sanitize_textarea_field($_POST['excluded_js']));
update_option('cwv_excluded_css', sanitize_textarea_field($_POST['excluded_css']));
echo '<div class="notice notice-success is-dismissible"><p>JS & CSS optimization settings saved!</p></div>';
}
// دریافت تنظیمات
$auto_optimize = get_option('cwv_auto_optimize_js_css', 0);
$excluded_js = get_option('cwv_excluded_js', 'jquery.js' . PHP_EOL . 'jquery.min.js' . PHP_EOL . 'wp-embed.min.js');
$excluded_css = get_option('cwv_excluded_css', 'admin-bar.min.css');
// دریافت آمار JS و CSS
global $wp_scripts, $wp_styles;
$js_count = count($wp_scripts->registered);
$css_count = count($wp_styles->registered);
?>
<div class="wrap">
<h1>Core Web Vitals JS & CSS Optimizer</h1>
<div class="notice notice-info">
<p>Optimize your JavaScript and CSS files to improve FID and LCP metrics and reduce render-blocking resources.</p>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 2; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Optimize JavaScript & CSS</h2>
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center;">
<div style="font-size: 36px; font-weight: bold; color: #0073aa;"><?php echo number_format($js_count); ?></div>
<div>JS Files</div>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center;">
<div style="font-size: 36px; font-weight: bold; color: #0073aa;"><?php echo number_format($css_count); ?></div>
<div>CSS Files</div>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center;">
<div style="font-size: 36px; font-weight: bold; color: #0073aa;">0%</div>
<div>Optimized</div>
</div>
</div>
<form method="post">
<h3>JavaScript Optimization</h3>
<table class="form-table">
<tr>
<th scope="row">Minify JavaScript</th>
<td>
<label><input type="checkbox" name="minify_js" checked> Remove whitespace and comments to reduce file size</label>
</td>
</tr>
<tr>
<th scope="row">Combine JavaScript</th>
<td>
<label><input type="checkbox" name="combine_js"> Merge multiple JS files into one (not recommended with HTTP/2)</label>
</td>
</tr>
<tr>
<th scope="row">Defer JavaScript</th>
<td>
<label><input type="checkbox" name="defer_js" checked> Add defer attribute to non-critical scripts</label>
</td>
</tr>
</table>
<h3>CSS Optimization</h3>
<table class="form-table">
<tr>
<th scope="row">Minify CSS</th>
<td>
<label><input type="checkbox" name="minify_css" checked> Remove whitespace and comments to reduce file size</label>
</td>
</tr>
<tr>
<th scope="row">Combine CSS</th>
<td>
<label><input type="checkbox" name="combine_css"> Merge multiple CSS files into one (not recommended with HTTP/2)</label>
</td>
</tr>
<tr>
<th scope="row">Extract Critical CSS</th>
<td>
<label><input type="checkbox" name="critical_css" checked> Inline critical CSS and defer non-critical CSS</label>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="optimize_js_css" class="button button-primary" value="Start Optimization">
</p>
</form>
<div class="progress-bar-container" style="height: 25px; background: #f1f1f1; border-radius: 4px; margin-top: 20px; display: none;">
<div class="progress-bar" style="width: 0%; height: 100%; background: #0073aa; border-radius: 4px; transition: width 0.3s;"></div>
</div>
<div class="progress-text" style="text-align: center; margin-top: 5px; display: none;">0% Complete</div>
</div>
<div style="flex: 1; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Settings</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">Auto-Optimization</th>
<td>
<label>
<input type="checkbox" name="auto_optimize_js_css" value="1" <?php checked($auto_optimize, 1); ?>>
Automatically optimize JS & CSS files
</label>
</td>
</tr>
<tr>
<th scope="row">Excluded JavaScript</th>
<td>
<textarea name="excluded_js" rows="5" cols="40" class="large-text"><?php echo esc_textarea($excluded_js); ?></textarea>
<p class="description">Enter one filename per line. These files will be excluded from optimization.</p>
</td>
</tr>
<tr>
<th scope="row">Excluded CSS</th>
<td>
<textarea name="excluded_css" rows="5" cols="40" class="large-text"><?php echo esc_textarea($excluded_css); ?></textarea>
<p class="description">Enter one filename per line. These files will be excluded from optimization.</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="save_js_css_settings" class="button button-primary" value="Save Settings">
</p>
</form>
</div>
</div>
<div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Detected JavaScript & CSS Files</h2>
<div style="display: flex; gap: 20px;">
<div style="flex: 1;">
<h3>JavaScript Files</h3>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Handle</th>
<th>Size</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
$js_files = array(
array('jquery', '33 KB', 'Excluded'),
array('jquery-migrate', '10 KB', 'Excluded'),
array('wp-embed', '2 KB', 'Excluded'),
array('theme-script', '45 KB', 'Not Optimized'),
array('slider-script', '78 KB', 'Not Optimized'),
array('comment-reply', '4 KB', 'Not Optimized')
);
foreach ($js_files as $file) {
echo '<tr>';
echo '<td>' . $file[0] . '</td>';
echo '<td>' . $file[1] . '</td>';
echo '<td>' . $file[2] . '</td>';
echo '</tr>';
}
?>
</tbody>
</table>
</div>
<div style="flex: 1;">
<h3>CSS Files</h3>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Handle</th>
<th>Size</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
$css_files = array(
array('admin-bar', '2 KB', 'Excluded'),
array('wp-block-library', '58 KB', 'Not Optimized'),
array('theme-style', '35 KB', 'Not Optimized'),
array('responsive-style', '12 KB', 'Not Optimized'),
array('font-awesome', '40 KB', 'Not Optimized')
);
foreach ($css_files as $file) {
echo '<tr>';
echo '<td>' . $file[0] . '</td>';
echo '<td>' . $file[1] . '</td>';
echo '<td>' . $file[2] . '</td>';
echo '</tr>';
}
?>
</tbody>
</table>
</div>
</div>
<p class="description" style="margin-top: 10px;">Note: This is sample data. Run the analysis to see your actual files.</p>
</div>
<div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>JS & CSS Optimization Tips for Core Web Vitals</h2>
<div style="display: flex; gap: 20px; margin-top: 15px;">
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>FID Improvement</h3>
<ul>
<li>Defer non-critical JavaScript</li>
<li>Break up long-running JavaScript tasks</li>
<li>Use Web Workers for heavy computations</li>
<li>Minimize unused JavaScript</li>
<li>Implement code-splitting for large applications</li>
</ul>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>LCP Improvement</h3>
<ul>
<li>Implement Critical CSS</li>
<li>Avoid render-blocking resources</li>
<li>Minify CSS and JavaScript</li>
<li>Use modern compression techniques</li>
<li>Implement resource hints (preload, prefetch)</li>
</ul>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>Best Practices</h3>
<ul>
<li>With HTTP/2, prefer multiple small files over combining</li>
<li>Use async for analytics scripts</li>
<li>Implement tree-shaking for JavaScript</li>
<li>Remove unused CSS</li>
<li>Consider module/nomodule pattern for modern browsers</li>
</ul>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('form[name="optimize_js_css"]').on('submit', function(e) {
// نمایش پیشرفت
$('.progress-bar-container, .progress-text').show();
// شبیهسازی پیشرفت
let progress = 0;
const interval = setInterval(function() {
progress += Math.random() * 15;
if (progress > 100) progress = 100;
$('.progress-bar').css('width', progress + '%');
$('.progress-text').text(Math.round(progress) + '% Complete');
if (progress === 100) {
clearInterval(interval);
setTimeout(function() {
alert('JavaScript and CSS optimization completed!');
}, 500);
}
}, 500);
// حذف این خط در پیادهسازی واقعی
// e.preventDefault();
});
});
</script>
<?php
}
3. ابزار بهینهسازی خودکار هاست و سرور
PHPfunction server_optimization_tool() {
if (is_admin() && current_user_can('manage_options')) {
add_submenu_page(
'core-web-vitals-monitor',
'Server Optimizer',
'Server Optimizer',
'manage_options',
'cwv-server-optimizer',
'render_server_optimizer'
);
}
}
add_action('admin_menu', 'server_optimization_tool', 60);
// رندر صفحه بهینهسازی سرور
function render_server_optimizer() {
// پردازش تنظیمات سرور
if (isset($_POST['apply_server_settings'])) {
$enable_gzip = isset($_POST['enable_gzip']) ? true : false;
$enable_browser_cache = isset($_POST['enable_browser_cache']) ? true : false;
$enable_keep_alive = isset($_POST['enable_keep_alive']) ? true : false;
// در یک پیادهسازی واقعی، اینجا تنظیمات سرور اعمال میشود
echo '<div class="notice notice-success is-dismissible"><p>Server settings applied successfully!</p></div>';
}
// دریافت اطلاعات سرور
$server_info = array(
'server_software' => $_SERVER['SERVER_SOFTWARE'],
'php_version' => PHP_VERSION,
'mysql_version' => function_exists('mysqli_get_client_info') ? mysqli_get_client_info() : 'Unknown',
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'post_max_size' => ini_get('post_max_size'),
'upload_max_filesize' => ini_get('upload_max_filesize'),
'max_input_vars' => ini_get('max_input_vars')
);
// بررسی تنظیمات سرور
$server_checks = array(
'gzip' => function_exists('ob_gzhandler'),
'keep_alive' => strpos(strtolower(isset($_SERVER['HTTP_CONNECTION']) ? $_SERVER['HTTP_CONNECTION'] : ''), 'keep-alive') !== false,
'http2' => isset($_SERVER['HTTP2']) || isset($_SERVER['H2_PUSH']),
'ssl' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'
);
?>
<div class="wrap">
<h1>Core Web Vitals Server Optimizer</h1>
<div class="notice notice-info">
<p>Optimize your server configuration to improve TTFB and overall Core Web Vitals performance.</p>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 2; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Server Information</h2>
<table class="wp-list-table widefat fixed striped">
<tbody>
<tr>
<th>Web Server</th>
<td><?php echo esc_html($server_info['server_software']); ?></td>
<td></td>
</tr>
<tr>
<th>PHP Version</th>
<td><?php echo esc_html($server_info['php_version']); ?></td>
<td>
<?php
if (version_compare($server_info['php_version'], '7.4', '>=')) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ff4e42;">✗ Upgrade to PHP 7.4+</span>';
}
?>
</td>
</tr>
<tr>
<th>MySQL Version</th>
<td><?php echo esc_html($server_info['mysql_version']); ?></td>
<td></td>
</tr>
<tr>
<th>PHP Memory Limit</th>
<td><?php echo esc_html($server_info['memory_limit']); ?></td>
<td>
<?php
$memory_limit = (int)$server_info['memory_limit'];
if ($memory_limit >= 256) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ffa400;">! Recommend 256M+</span>';
}
?>
</td>
</tr>
<tr>
<th>Max Execution Time</th>
<td><?php echo esc_html($server_info['max_execution_time']); ?> seconds</td>
<td>
<?php
if ($server_info['max_execution_time'] >= 60) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ffa400;">! Recommend 60+</span>';
}
?>
</td>
</tr>
<tr>
<th>GZIP Compression</th>
<td><?php echo $server_checks['gzip'] ? 'Enabled' : 'Disabled'; ?></td>
<td>
<?php
if ($server_checks['gzip']) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ff4e42;">✗ Enable GZIP</span>';
}
?>
</td>
</tr>
<tr>
<th>Keep-Alive</th>
<td><?php echo $server_checks['keep_alive'] ? 'Enabled' : 'Disabled'; ?></td>
<td>
<?php
if ($server_checks['keep_alive']) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ff4e42;">✗ Enable Keep-Alive</span>';
}
?>
</td>
</tr>
<tr>
<th>HTTP/2</th>
<td><?php echo $server_checks['http2'] ? 'Enabled' : 'Disabled'; ?></td>
<td>
<?php
if ($server_checks['http2']) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ff4e42;">✗ Enable HTTP/2</span>';
}
?>
</td>
</tr>
<tr>
<th>SSL/HTTPS</th>
<td><?php echo $server_checks['ssl'] ? 'Enabled' : 'Disabled'; ?></td>
<td>
<?php
if ($server_checks['ssl']) {
echo '<span style="color: #0cce6b;">✓ Good</span>';
} else {
echo '<span style="color: #ff4e42;">✗ Enable SSL</span>';
}
?>
</td>
</tr>
</tbody>
</table>
<div style="margin-top: 20px;">
<h2>Server Optimization</h2>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">GZIP Compression</th>
<td>
<label><input type="checkbox" name="enable_gzip" <?php checked($server_checks['gzip'], true); ?>> Enable GZIP compression</label>
<p class="description">Reduces the size of transferred files by up to 70%.</p>
</td>
</tr>
<tr>
<th scope="row">Browser Caching</th>
<td>
<label><input type="checkbox" name="enable_browser_cache" checked> Enable browser caching</label>
<p class="description">Sets appropriate cache headers for static assets.</p>
</td>
</tr>
<tr>
<th scope="row">Keep-Alive</th>
<td>
<label><input type="checkbox" name="enable_keep_alive" <?php checked($server_checks['keep_alive'], true); ?>> Enable Keep-Alive connections</label>
<p class="description">Reuses the same TCP connection for multiple requests.</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="apply_server_settings" class="button button-primary" value="Apply Server Settings">
</p>
</form>
</div>
<div style="margin-top: 20px;">
<h2>Generated Configuration Files</h2>
<div style="display: flex; gap: 20px;">
<div style="flex: 1;">
<h3>.htaccess (Apache)</h3>
<textarea readonly style="width: 100%; height: 200px; font-family: monospace; background: #f1f1f1; padding: 10px;"># BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# BEGIN Core Web Vitals Optimization
# Enable GZIP Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain text/html text/xml text/css text/javascript application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript application/json
</IfModule>
# Enable Browser Caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType text/html "access plus 1 day"
</IfModule>
# Enable Keep-Alive
<IfModule mod_headers.c>
Header set Connection keep-alive
</IfModule>
# END Core Web Vitals Optimization</textarea>
</div>
<div style="flex: 1;">
<h3>nginx.conf (Nginx)</h3>
<textarea readonly style="width: 100%; height: 200px; font-family: monospace; background: #f1f1f1; padding: 10px;"># Core Web Vitals Optimization for Nginx
# Enable GZIP Compression
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
text/js
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/rss+xml
image/svg+xml;
# Enable Browser Caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Enable HTTP/2
listen 443 ssl http2;
# Enable Keep-Alive
keepalive_timeout 65;
keepalive_requests 100;</textarea>
</div>
</div>
</div>
</div>
<div style="flex: 1; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>TTFB Analysis</h2>
<div style="background: #f9f9f9; padding: 15px; border-radius: 5px; text-align: center; margin-bottom: 20px;">
<div style="font-size: 36px; font-weight: bold; color: #ffa400;">320 ms</div>
<div>Average TTFB</div>
</div>
<h3>TTFB Breakdown</h3>
<div style="height: 20px; background: #f1f1f1; border-radius: 10px; margin-bottom: 15px; overflow: hidden;">
<div style="width: 30%; height: 100%; background: #ff4e42; float: left;" title="Server Processing: 150ms"></div>
<div style="width: 15%; height: 100%; background: #ffa400; float: left;" title="Network: 75ms"></div>
<div style="width: 10%; height: 100%; background: #0cce6b; float: left;" title="SSL: 50ms"></div>
<div style="width: 9%; height: 100%; background: #0073aa; float: left;" title="DNS: 45ms"></div>
</div>
<div style="display: flex; margin-bottom: 20px; font-size: 12px;">
<div style="flex: 30; color: #ff4e42;">Server (150ms)</div>
<div style="flex: 15; color: #ffa400;">Network (75ms)</div>
<div style="flex: 10; color: #0cce6b;">SSL (50ms)</div>
<div style="flex: 9; color: #0073aa;">DNS (45ms)</div>
</div>
<h3>Server Recommendations</h3>
<ul>
<li><strong>Upgrade PHP Version:</strong> Moving to PHP 7.4+ can improve processing speed by up to 30%.</li>
<li><strong>Implement Object Caching:</strong> Use Redis or Memcached to cache database queries.</li>
<li><strong>Enable OpCache:</strong> PHP's OpCache can significantly reduce script execution time.</li>
<li><strong>Consider CDN:</strong> A CDN can reduce network latency for global visitors.</li>
<li><strong>Use HTTP/2:</strong> HTTP/2 offers multiplexing and header compression.</li>
</ul>
<h3>PHP Optimization</h3>
<p>Recommended PHP settings for optimal performance:</p>
<pre style="background: #f1f1f1; padding: 10px; font-family: monospace;">memory_limit = 256M
max_execution_time = 300
post_max_size = 64M
upload_max_filesize = 32M
max_input_vars = 3000
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 2
opcache.save_comments = 1</pre>
<div style="margin-top: 20px;">
<a href="#" class="button button-primary">Generate PHP.ini File</a>
<a href="#" class="button">Test TTFB</a>
</div>
</div>
</div>
<div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2>Server Optimization Tips for Core Web Vitals</h2>
<div style="display: flex; gap: 20px; margin-top: 15px;">
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>TTFB Improvement</h3>
<ul>
<li>Upgrade to a better hosting plan</li>
<li>Implement object caching (Redis/Memcached)</li>
<li>Optimize database queries</li>
<li>Enable PHP OpCache</li>
<li>Use a CDN to reduce latency</li>
</ul>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>HTTP Optimization</h3>
<ul>
<li>Enable HTTP/2 or HTTP/3</li>
<li>Implement GZIP compression</li>
<li>Set proper cache headers</li>
<li>Enable Keep-Alive connections</li>
<li>Use server-side caching</li>
</ul>
</div>
<div style="flex: 1; background: #f9f9f9; padding: 15px; border-radius: 5px;">
<h3>Best Practices</h3>
<ul>
<li>Regularly update PHP version</li>
<li>Monitor server performance</li>
<li>Optimize WordPress database</li>
<li>Use a dedicated hosting environment</li>
<li>Implement a proper caching strategy</li>
</ul>
</div>
</div>
</div>
</div>
<?php
}
نظارت مستمر بر Core Web Vitals و بهینهسازی مداوم آنها یک فرآیند ضروری برای حفظ و بهبود عملکرد سایت وردپرسی است. با استفاده از ابزارهای ارائه شده در این بخش، میتوانید به طور منظم Core Web Vitals را بررسی کرده و اقدامات لازم برای بهبود آنها را انجام دهید.
در بخش بعدی و نهایی، به بررسی یک مطالعه موردی از بهینهسازی موفق یک سایت وردپرسی برای دستیابی به امتیاز 100 در Lighthouse خواهیم پرداخت.
22. چگونه یک سایت وردپرسی را به امتیاز 100 در Lighthouse برسانیم
در این بخش، به بررسی یک مطالعه موردی واقعی از بهینهسازی یک سایت وردپرسی برای دستیابی به امتیاز 100 در Lighthouse میپردازیم. این مطالعه موردی به شما نشان میدهد که چگونه تکنیکهای مختلف بهینهسازی وردپرس در عمل پیادهسازی میشوند و چه نتایجی به همراه دارند.
معرفی پروژه
وضعیت اولیه سایت
سایت مورد مطالعه ما یک فروشگاه آنلاین وردپرسی با ووکامرس است که با مشکلات عملکردی زیر مواجه بود:
- امتیاز Lighthouse موبایل: 35/100
- امتیاز Lighthouse دسکتاپ: 58/100
- LCP: 5.2 ثانیه (ضعیف)
- FID: 350 میلیثانیه (ضعیف)
- CLS: 0.32 (ضعیف)
- TTFB: 1.8 ثانیه
- تعداد درخواستها: 95
- حجم کل صفحه: 4.2 مگابایت
سایت از یک قالب ووکامرس تجاری استفاده میکرد و 25 افزونه فعال داشت. هاست آن یک سرور اشتراکی معمولی بود و از PHP 7.2 استفاده میکرد.
فرآیند بهینهسازی
مرحله 1: تحلیل و شناسایی مشکلات
ابتدا با استفاده از ابزارهای مختلف، مشکلات اصلی سایت را شناسایی کردیم:
- TTFB بالا: سرور کند و کوئریهای ناکارآمد دیتابیس
- تصاویر بزرگ و بهینهنشده: تصاویر محصولات و اسلایدرها بدون بهینهسازی
- JavaScript و CSS غیرضروری: بارگذاری اسکریپتها در تمام صفحات
- افزونههای متعدد: تداخل و بارگذاری منابع متعدد
- عدم استفاده از کش: بدون کش سرور یا مرورگر
- تصاویر بدون ابعاد: باعث CLS بالا میشد
- فونتهای وب متعدد: بارگذاری چندین فونت بدون بهینهسازی
مرحله 2: ارتقای هاست و سرور
اولین گام، ارتقای زیرساخت سرور بود:
- ارتقا به هاست مدیریت شده وردپرس: انتقال به یک هاست تخصصی وردپرس با منابع اختصاصی
- ارتقای PHP به نسخه 8.0: بهبود قابل توجه در سرعت پردازش
- نصب Redis برای Object Cache: کش کردن نتایج کوئریها و APIها
- فعالسازی HTTP/2: امکان بارگذاری همزمان منابع متعدد
- تنظیم GZIP و فشردهسازی: کاهش حجم دادههای منتقل شده
- بهینهسازی MySQL: افزودن ایندکسهای مناسب و بهینهسازی جداول
نتیجه این مرحله، کاهش TTFB از 1.8 ثانیه به 380 میلیثانیه بود.
مرحله 3: بهینهسازی تصاویر
تصاویر یکی از بزرگترین مشکلات سایت بودند:
- تبدیل تمام تصاویر به WebP: استفاده از فرمت مدرن با فشردهسازی بهتر
- اضافه کردن width و height به تمام تصاویر: جلوگیری از CLS
- پیادهسازی Lazy Loading: بارگذاری تصاویر فقط هنگام نیاز
- تنظیم srcset برای تصاویر ریسپانسیو: ارائه تصاویر با اندازه مناسب برای هر دستگاه
- فشردهسازی تصاویر: کاهش حجم بدون افت کیفیت قابل توجه
- استفاده از CDN برای تصاویر: توزیع تصاویر از سرورهای نزدیک به کاربر
کد اضافه شده به functions.php برای بهینهسازی تصاویر:
PHP// تبدیل خودکار تصاویر به WebP
function convert_images_to_webp($file) {
if (!function_exists('imagewebp')) return $file;
$info = pathinfo($file['file']);
$ext = strtolower($info['extension']);
if (!in_array($ext, ['jpg', 'jpeg', 'png'])) return $file;
$webp_file = $info['dirname'] . '/' . $info['filename'] . '.webp';
if ($ext === 'png') {
$image = imagecreatefrompng($file['file']);
imagepalettetotruecolor($image);
imagealphablending($image, true);
imagesavealpha($image, true);
} else {
$image = imagecreatefromjpeg($file['file']);
}
imagewebp($image, $webp_file, 80);
imagedestroy($image);
// اضافه کردن فایل WebP به آرایه sizes
$file['sizes']['webp'] = [
'file' => basename($webp_file),
'width' => $file['width'],
'height' => $file['height'],
'mime-type' => 'image/webp'
];
return $file;
}
add_filter('wp_generate_attachment_metadata', 'convert_images_to_webp');
// استفاده از تصاویر WebP در محتوا
function use_webp_images($content) {
if (empty($content)) return $content;
return preg_replace_callback('/<img(.*?)src=(["\'])(.*?)\.(jpe?g|png)(["\'])(.*?)>/i', function($matches) {
$img_tag = $matches[0];
$img_src = $matches[3] . '.' . $matches[4];
$webp_src = $matches[3] . '.webp';
// بررسی وجود فایل WebP
$upload_dir = wp_upload_dir();
$webp_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $webp_src);
if (file_exists($webp_path)) {
return '<picture>
<source srcset="' . $webp_src . '" type="image/webp">
<img' . $matches[1] . 'src=' . $matches[2] . $img_src . $matches[5] . $matches[6] . '>
</picture>';
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'use_webp_images');
add_filter('woocommerce_product_get_description', 'use_webp_images');
add_filter('woocommerce_short_description', 'use_webp_images');
// اضافه کردن width و height به تمام تصاویر
function add_image_dimensions($content) {
return preg_replace_callback('/<img(.*?)>/i', function($matches) {
$img_tag = $matches[0];
// اگر width و height وجود دارند، تغییری ایجاد نکن
if (strpos($img_tag, 'width=') !== false && strpos($img_tag, 'height=') !== false) {
return $img_tag;
}
// استخراج URL تصویر
preg_match('/src="([^"]*)"/', $img_tag, $src_match);
if (empty($src_match[1])) return $img_tag;
$src = $src_match[1];
$upload_dir = wp_upload_dir();
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $src);
if (file_exists($file_path)) {
list($width, $height) = getimagesize($file_path);
return str_replace('<img', '<img width="' . $width . '" height="' . $height . '"', $img_tag);
}
return $img_tag;
}, $content);
}
add_filter('the_content', 'add_image_dimensions');
add_filter('post_thumbnail_html', 'add_image_dimensions');
add_filter('woocommerce_product_get_description', 'add_image_dimensions');
نتیجه این مرحله، کاهش حجم کل صفحه از 4.2 مگابایت به 1.3 مگابایت و بهبود LCP به 3.1 ثانیه بود.
مرحله 4: بهینهسازی JavaScript و CSS
در این مرحله، فایلهای JavaScript و CSS را بهینه کردیم:
- حذف JavaScript و CSS غیرضروری: شناسایی و حذف کدهای استفاده نشده
- به تعویق انداختن JavaScript: استفاده از defer برای اسکریپتهای غیرضروری
- استخراج Critical CSS: بارگذاری CSS ضروری به صورت درونخطی
- بارگذاری شرطی اسکریپتها: بارگذاری اسکریپتها فقط در صفحات مورد نیاز
- مینیفای کردن فایلها: حذف فضاهای خالی و کامنتها
کد اضافه شده به functions.php:
PHP// به تعویق انداختن JavaScript
function defer_parsing_of_js($url) {
if (is_admin()) return $url;
// اسکریپتهایی که نباید به تعویق بیفتند
$do_not_defer = array('jquery.js', 'jquery.min.js');
foreach ($do_not_defer as $file) {
if (strpos($url, $file) !== false) return $url;
}
return str_replace(' src', ' defer src', $url);
}
add_filter('script_loader_tag', 'defer_parsing_of_js', 10, 1);
// استخراج Critical CSS
function add_critical_css() {
if (is_front_page() || is_home()) {
echo '<style id="critical-css">' . file_get_contents(get_template_directory() . '/assets/css/critical-home.css') . '</style>';
} elseif (is_product()) {
echo '<style id="critical-css">' . file_get_contents(get_template_directory() . '/assets/css/critical-product.css') . '</style>';
} elseif (is_shop()) {
echo '<style id="critical-css">' . file_get_contents(get_template_directory() . '/assets/css/critical-shop.css') . '</style>';
} else {
echo '<style id="critical-css">' . file_get_contents(get_template_directory() . '/assets/css/critical-global.css') . '</style>';
}
}
add_action('wp_head', 'add_critical_css', 1);
// بارگذاری شرطی اسکریپتهای ووکامرس
function conditionally_load_woocommerce_scripts() {
if (!is_woocommerce() && !is_cart() && !is_checkout() && !is_account_page()) {
// حذف اسکریپتهای ووکامرس در صفحات غیر فروشگاهی
wp_dequeue_style('woocommerce-general');
wp_dequeue_style('woocommerce-layout');
wp_dequeue_style('woocommerce-smallscreen');
wp_dequeue_script('woocommerce');
wp_dequeue_script('wc-cart-fragments');
wp_dequeue_script('wc-add-to-cart');
}
}
add_action('wp_enqueue_scripts', 'conditionally_load_woocommerce_scripts', 99);
// بارگذاری شرطی اسکریپتهای اسلایدر
function conditionally_load_slider_scripts() {
if (!is_front_page() && !has_shortcode(get_post()->post_content, 'slider')) {
wp_dequeue_style('slider-style');
wp_dequeue_script('slider-script');
}
}
add_action('wp_enqueue_scripts', 'conditionally_load_slider_scripts', 99);
نتیجه این مرحله، کاهش تعداد درخواستها از 95 به 42 و بهبود FID به 120 میلیثانیه بود.
مرحله 5: بهینهسازی فونتها
فونتها یکی از عوامل اصلی CLS بالا بودند:
- کاهش تعداد فونتها: استفاده از 2 فونت به جای 5 فونت
- استفاده از font-display: swap: جلوگیری از تأخیر در نمایش متن
- پیشبارگذاری فونتهای ضروری: استفاده از preload برای فونتهای مهم
- میزبانی محلی فونتهای گوگل: حذف درخواستهای خارجی
- استفاده از فونتهای متغیر: کاهش تعداد فایلهای فونت
کد اضافه شده:
PHP// میزبانی محلی فونتهای گوگل
function host_google_fonts_locally() {
// حذف فونتهای گوگل
wp_dequeue_style('google-fonts');
// اضافه کردن فونتهای محلی
wp_enqueue_style('local-fonts', get_template_directory_uri() . '/assets/fonts/fonts.css', array(), '1.0.0');
}
add_action('wp_enqueue_scripts', 'host_google_fonts_locally', 999);
// پیشبارگذاری فونتهای ضروری
function preload_critical_fonts() {
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/fonts/primary-font.woff2" as="font" type="font/woff2" crossorigin>';
}
add_action('wp_head', 'preload_critical_fonts', 1);
فایل fonts.css:
CSS/* فونت اصلی - نسخه متغیر */
@font-face {
font-family: 'Primary Font';
src: url('primary-font-var.woff2') format('woff2-variations');
font-weight: 300 700;
font-style: normal;
font-display: swap;
}
/* فونت ثانویه */
@font-face {
font-family: 'Secondary Font';
src: url('secondary-font.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
نتیجه این مرحله، کاهش CLS از 0.32 به 0.08 بود.
مرحله 6: پیادهسازی کش و CDN
برای بهبود بیشتر عملکرد، سیستم کش و CDN را پیادهسازی کردیم:
- نصب افزونه WP Rocket: پیادهسازی کش صفحه، مینیفای و بهینهسازی
- راهاندازی Cloudflare: به عنوان CDN و لایه امنیتی
- تنظیم هدرهای کش مناسب: برای فایلهای استاتیک
- پیادهسازی کش مرورگر: با استفاده از Cache-Control
- فعالسازی HTTP/2 Server Push: برای منابع حیاتی
تنظیمات کلید WP Rocket:
Code// تنظیمات اصلی
File Optimization:
- Minify CSS: On
- Combine CSS: Off (better for HTTP/2)
- Minify JavaScript: On
- Combine JavaScript: Off (better for HTTP/2)
- Defer JavaScript: On
- Delay JavaScript Execution: On
Media:
- LazyLoad: On
- Image Dimensions: On
- WebP Caching: On
Preload:
- Preload Cache: On
- Prefetch DNS Requests: On (for critical domains)
- Preload Fonts: On (for critical fonts)
تنظیمات Cloudflare:
Code// تنظیمات Cloudflare
Performance:
- Auto Minify: JavaScript, CSS, HTML
- Brotli: On
- Rocket Loader: Off (conflicts with our optimizations)
- HTTP/2/3: On
- Early Hints: On
- Polish: WebP, Lossy compression
- Tiered Cache: On
- Cache Level: Standard
نتیجه این مرحله، کاهش TTFB به 180 میلیثانیه و بهبود LCP به 2.1 ثانیه بود.
مرحله 7: رفع مشکلات باقیمانده
در مرحله نهایی، مشکلات باقیمانده را برطرف کردیم:
- حذف افزونههای غیرضروری: کاهش تعداد افزونهها از 25 به 14
- بهینهسازی دیتابیس: پاکسازی و بهینهسازی جداول
- بهینهسازی فایل functions.php: حذف کدهای غیرضروری
- استفاده از resource hints: preconnect، prefetch و preload
- بهینهسازی قالب: حذف ویژگیهای غیرضروری قالب
کد resource hints اضافه شده:
PHPfunction add_resource_hints() {
// Preconnect
echo '<link rel="preconnect" href="https://cdn.example.com" crossorigin>';
// Preload hero image on homepage
if (is_front_page()) {
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/images/hero.webp" as="image" type="image/webp">';
}
// Preload logo
echo '<link rel="preload" href="' . get_template_directory_uri() . '/assets/images/logo.svg" as="image" type="image/svg+xml">';
// Prefetch important pages
if (is_front_page()) {
echo '<link rel="prefetch" href="' . home_url('/shop/') . '">';
echo '<link rel="prefetch" href="' . home_url('/cart/') . '">';
}
}
add_action('wp_head', 'add_resource_hints', 1);
نتایج نهایی
پس از اجرای تمام این بهینهسازیها، نتایج زیر به دست آمد:
امتیازات Lighthouse:
- موبایل: 100/100 (از 35/100)
- دسکتاپ: 100/100 (از 58/100)
Core Web Vitals:
- LCP: 1.8 ثانیه (از 5.2 ثانیه) – خوب
- FID: 65 میلیثانیه (از 350 میلیثانیه) – خوب
- CLS: 0.05 (از 0.32) – خوب
- TTFB: 180 میلیثانیه (از 1.8 ثانیه)
سایر بهبودها:
- تعداد درخواستها: 28 (از 95)
- حجم کل صفحه: 890 کیلوبایت (از 4.2 مگابایت)
- زمان بارگذاری کامل: 2.3 ثانیه (از 8.7 ثانیه)
درسآموختهها
این مطالعه موردی چند درس مهم برای بهینهسازی وردپرس به ما میآموزد:
- رویکرد جامع: بهینهسازی باید همه جنبهها از هاست گرفته تا کدها را پوشش دهد.
- اهمیت هاست: یک هاست خوب میتواند تأثیر چشمگیری بر عملکرد داشته باشد.
- کمتر، بهتر است: کاهش تعداد افزونهها، فایلها و درخواستها بسیار مهم است.
- بهینهسازی تصاویر: تصاویر معمولاً بزرگترین منبع مشکلات هستند.
- اندازهگیری مداوم: بهینهسازی یک فرآیند مداوم است که نیاز به اندازهگیری منظم دارد.
- توجه به موبایل: بهینهسازی باید با اولویت موبایل انجام شود.
- اهمیت CDN: استفاده از CDN تأثیر قابل توجهی بر عملکرد جهانی دارد.
نتیجهگیری
این مطالعه موردی نشان میدهد که دستیابی به امتیاز 100 در Lighthouse برای سایتهای وردپرسی، حتی سایتهای پیچیده مانند فروشگاههای آنلاین، امکانپذیر است. با رویکردی سیستماتیک و اجرای تکنیکهای مناسب بهینهسازی وردپرس، میتوان Core Web Vitals را به طور قابل توجهی بهبود بخشید.
بهینهسازی Core Web Vitals نه تنها رتبهبندی سایت در موتورهای جستجو را بهبود میبخشد، بلکه تجربه کاربری بهتری نیز ارائه میدهد که میتواند به افزایش نرخ تبدیل و وفاداری کاربران منجر شود. این سرمایهگذاری زمان و منابع، بازگشت سرمایه قابل توجهی در بلندمدت خواهد داشت.
امیدواریم این مطالعه موردی و راهنمای جامع بهینهسازی Core Web Vitals به شما در بهبود عملکرد سایت وردپرسی خود کمک کند. به یاد داشته باشید که بهینهسازی یک سفر مداوم است، نه یک مقصد نهایی.