مقالات › وب و سئو

آموزش Core Web Vitals؛ بهبود LCP، INP و CLS برای سئوی بهتر

راهنمای عملی Core Web Vitals: معنای LCP، INP و CLS، روش اندازه‌گیری درست و راه‌حل‌های مشخص برای تصاویر، فونت، جاوااسکریپت و React برای سئوی بهتر.

سرعت فقط یک امتیاز اضافه نیست: صفحه‌ی کند بازدیدکننده، نرخ تبدیل و رتبه‌ی جست‌وجو را از دست می‌دهد. گوگل تجربه‌ی واقعی کاربران را با سه شاخص Core Web Vitals می‌سنجد و از آن‌ها به‌عنوان سیگنال رتبه‌بندی استفاده می‌کند. این راهنما هر شاخص، روش درست اندازه‌گیری و راه‌حل‌هایی را که واقعاً اعداد را تغییر می‌دهند توضیح می‌دهد.

سه شاخص اصلی

شاخص چه چیزی را می‌سنجد خوب نیاز به بهبود ضعیف
LCP — Largest Contentful Paint بارگذاری: زمان نمایش محتوای اصلی ≤ ۲٫۵ ثانیه ≤ ۴ ثانیه > ۴ ثانیه
INP — Interaction to Next Paint پاسخ‌گویی: تأخیر بعد از کلیک، لمس یا کلید ≤ ۲۰۰ میلی‌ثانیه ≤ ۵۰۰ میلی‌ثانیه > ۵۰۰ میلی‌ثانیه
CLS — Cumulative Layout Shift پایداری بصری: جابه‌جایی ناخواسته ≤ ۰٫۱ ≤ ۰٫۲۵ > ۰٫۲۵

یک صفحه وقتی «قبول» می‌شود که ۷۵٪ بازدیدهای واقعی در هر سه شاخص در محدوده‌ی خوب باشند. INP در سال ۲۰۲۴ جایگزین FID شد و برخلاف آن، همه‌ی تعامل‌های طول بازدید را در نظر می‌گیرد، نه فقط اولی را.

داده‌ی میدانی و داده‌ی آزمایشگاهی

  • داده‌ی میدانی (RUM) از کاربران واقعی کروم می‌آید (Chrome UX Report). گوگل برای رتبه‌بندی از همین استفاده می‌کند. آن را در PageSpeed Insights (بخش تجربه‌ی کاربران واقعی) و Search Console ← Core Web Vitals ببینید.
  • داده‌ی آزمایشگاهی از یک آزمون شبیه‌سازی‌شده می‌آید (Lighthouse و پنل Performance در DevTools). برای عیب‌یابی و تکرارپذیری عالی است، اما امتیاز رتبه‌بندی شما نیست.

داده‌ی میدانی خودتان را با کتابخانه‌ی web-vitals جمع کنید:

import { onLCP, onINP, onCLS } from "web-vitals";

function send(metric) {
  navigator.sendBeacon("/api/vitals", JSON.stringify({
    name: metric.name, value: metric.value, rating: metric.rating, page: location.pathname,
  }));
}
onLCP(send); onINP(send); onCLS(send);

نکته: همیشه روی یک گوشی میان‌رده با شبکه‌ی محدودشده تست کنید. لپ‌تاپ توسعه با اینترنت پرسرعت تقریباً همه‌ی مشکلات کاربران را پنهان می‌کند؛ به‌خصوص برای کاربران ایرانی با اینترنت موبایل ناپایدار.

بهبود LCP

عنصر LCP معمولاً تصویر Hero، یک عنوان بزرگ یا بنر است. LCP به چهار بخش تقسیم می‌شود؛ بزرگ‌ترین را اصلاح کنید:

  1. زمان دریافت اولین بایت (TTFB) — زمان پاسخ سرور.
  2. تأخیر بارگذاری منبع — مرورگر تصویر LCP را چقدر دیر کشف می‌کند.
  3. زمان بارگذاری منبع — دانلود تصویر چقدر طول می‌کشد.
  4. تأخیر رندر — CSS/JS مسدودکننده یا رندر سمت کلاینت که نمایش را عقب می‌اندازد.

راه‌حل‌ها

تصویر LCP را قابل‌کشف و با اولویت بالا کنید:

<img src="/images/hero-1200.avif"
     srcset="/images/hero-600.avif 600w, /images/hero-1200.avif 1200w"
     sizes="(max-width: 768px) 100vw, 50vw"
     width="1200" height="800" alt="داشبورد پروژه"
     fetchpriority="high">
  • هرگز روی تصویر LCP از loading="lazy" استفاده نکنید (Lazy Loading فقط برای تصاویر پایین‌تر از دید اول).
  • اگر تصویر پس‌زمینه‌ی CSS است یا با جاوااسکریپت اضافه می‌شود، مرورگر دیر پیدایش می‌کند؛ از <img> یا <link rel="preload" as="image" fetchpriority="high"> استفاده کنید.
  • از AVIF/WebP، ابعاد درست و srcset ریسپانسیو استفاده کنید. تصویر Hero با فرمت PNG و حجم ۳ مگابایت رایج‌ترین مشکل LCP است.

کاهش TTFB: صفحات رندرشده را کش کنید، از CDN استفاده کنید، فشرده‌سازی (Brotli/gzip) را فعال کنید، کوئری‌های کند دیتابیس را بهینه کنید و سرور را نزدیک کاربرانتان نگه دارید.

برای صفحات محتوایی از رندر فقط سمت کلاینت پرهیز کنید: یک SPA ری‌اکتی تا وقتی باندل JS دانلود، Parse و داده‌ها گرفته نشوند چیزی نشان نمی‌دهد. رندر سمت سرور یا Prerender (مثل Next.js، یا تزریق محتوا در سمت سرور در یک میزبان ASP.NET Core) محتوا را در اولین پاسخ HTML قرار می‌دهد.

CSS حیاتی را Inline و بقیه را با تأخیر بارگذاری کنید و اسکریپت‌های مسدودکننده‌ی شخص ثالث را از <head> بردارید.

بهبود INP

INP وقتی بد است که ترد اصلی مشغول است؛ تسک‌های طولانی جاوااسکریپت مانع پاسخ مرورگر به ورودی کاربر می‌شوند.

راه‌حل‌ها

تسک‌های طولانی را بشکنید. هر چیزی بیش از ۵۰ میلی‌ثانیه تعامل را مسدود می‌کند. بین بخش‌ها کنترل را به مرورگر بدهید:

const yieldToMain = () =>
  globalThis.scheduler?.yield ? scheduler.yield() : new Promise((r) => setTimeout(r, 0));

async function processItems(items) {
  for (let i = 0; i < items.length; i++) {
    handle(items[i]);
    if (i % 100 === 0) await yieldToMain();
  }
}

فوراً بازخورد بصری بدهید و بعد کار سنگین را انجام دهید:

function FilterList({ items }) {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);              // ورودی پاسخ‌گو می‌ماند
  const filtered = useMemo(() => items.filter((i) => i.title.includes(deferredQuery)), [items, deferredQuery]);
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <List items={filtered} />
    </>
  );
}

در React، useTransition و useDeferredValue به‌روزرسانی‌های سنگین را غیرفوری علامت می‌زنند تا تایپ و کلیک سریع بمانند.

جاوااسکریپت کمتری بفرستید: مسیرها را Code Split کنید (React.lazy)، کتابخانه‌های استفاده‌نشده را حذف کنید، وابستگی‌های سنگین را جایگزین کنید (moment.js با Intl بومی یا کتابخانه‌ی کوچک تاریخ) و اسکریپت‌های آنالیتیکس و چت را بعد از تعامل یا در زمان بیکاری بارگذاری کنید.

از DOM بسیار بزرگ و Layout Thrashing دوری کنید: لیست‌های طولانی را مجازی (Virtualize) کنید و در حلقه بلافاصله بعد از نوشتن استایل، Layout (مثل offsetHeight) را نخوانید.

محاسبات سنگین را به Web Worker منتقل کنید (Parse فایل‌های بزرگ، پردازش تصویر).

بهبود CLS

جابه‌جایی Layout وقتی رخ می‌دهد که محتوا بعد از نمایش تکان بخورد؛ همان «می‌خواستم دکمه را بزنم ولی تبلیغ آن را پایین برد».

راه‌حل‌ها

همیشه برای رسانه‌ها جا رزرو کنید:

<img src="photo.webp" width="800" height="600" alt="...">
.video-embed { aspect-ratio: 16 / 9; width: 100%; }
img { max-width: 100%; height: auto; }

برای محتوای پویا جا رزرو کنید؛ بنرها، تبلیغات، اعلان کوکی و کامپوننت‌هایی که دیر بارگذاری می‌شوند به min-height یا Skeleton با اندازه‌ی نهایی نیاز دارند.

فونت‌ها را بدون جابه‌جایی متن بارگذاری کنید:

@font-face {
  font-family: "Vazirmatn";
  src: url("/fonts/vazirmatn.woff2") format("woff2");
  font-display: swap;
  size-adjust: 100%;
}

فونت‌ها را روی سرور خودتان میزبانی کنید، فونت اصلی را Preload کنید، از font-display: swap یا optional استفاده کنید و فونت جایگزینی با ابعاد مشابه انتخاب کنید (size-adjust، ascent-override) تا جابه‌جایی هنگام تعویض فونت حداقل شود.

با transform و opacity انیمیشن بسازید، هرگز با top، height یا margin؛ انیمیشن‌های Transform باعث جابه‌جایی Layout نمی‌شوند.

محتوا را بالای محتوای موجود اضافه نکنید، مگر در پاسخ به عمل کاربر (جابه‌جایی‌هایی که تا ۵۰۰ میلی‌ثانیه بعد از تعامل رخ دهند محاسبه نمی‌شوند).

چک‌لیست سریع بررسی

  • تصویر LCP یک <img> با fetchpriority="high"، فرمت مدرن و اندازه‌ی درست است
  • در دید اول Lazy Loading نداریم؛ پایین‌تر از دید اول داریم
  • HTML فشرده و کش می‌شود؛ TTFB زیر حدود ۸۰۰ میلی‌ثانیه است
  • مسیرها Code Split شده‌اند و باندل JS با Bundle Analyzer بررسی شده است
  • اسکریپت‌های شخص ثالث با تأخیر بارگذاری یا حذف شده‌اند
  • همه‌ی تصاویر، ویدیوها و Embedها ابعاد یا aspect-ratio دارند
  • فونت‌ها لوکال، Preload‌شده و با font-display هستند
  • لیست‌های طولانی Virtualize شده‌اند و به‌روزرسانی‌های سنگین در Transition هستند

کارایی و سئو

Core Web Vitals یک فاکتور رتبه‌بندی است، اما مرتبط بودن محتوا اولویت دارد؛ صفحه‌ی سریع با محتوای ضعیف از پاسخ بهتر و کندتر جلو نمی‌زند. وقتی محتواها قابل مقایسه‌اند، تجربه‌ی کاربری می‌تواند تعیین‌کننده باشد. مهم‌تر این‌که سرعت مستقیماً نرخ پرش و نرخ تبدیل را بهتر می‌کند و این مستقل از رتبه اهمیت دارد.

جمع‌بندی

LCP می‌سنجد محتوای اصلی چقدر سریع ظاهر می‌شود، INP صفحه چقدر سریع پاسخ می‌دهد و CLS چقدر پایدار است. از داده‌ی میدانی برای دانستن وضعیت و از ابزارهای آزمایشگاهی برای یافتن علت استفاده کنید. تصویر Hero را اولویت‌دار و بهینه کنید، TTFB را کم و محتوا را در سرور رندر کنید، جاوااسکریپت کمتری بفرستید و تسک‌های طولانی را بشکنید و برای هر چیزی که دیر بارگذاری می‌شود جا رزرو کنید. اصلاحات کوچک و هدفمند اغلب یک صفحه‌ی مردود را قبول می‌کنند.

پرسش‌های متداول

چرا امتیاز Lighthouse بالاست اما Search Console می‌گوید «ضعیف»؟

Lighthouse یک بارگذاری شبیه‌سازی‌شده است؛ Search Console تجربه‌ی کاربران واقعی روی دستگاه‌ها و شبکه‌های واقعی در ۲۸ روز را نشان می‌دهد. برای سئو به داده‌ی میدانی اعتماد کنید.

بهبودها چه زمانی در Search Console دیده می‌شوند؟

داده‌ی میدانی یک پنجره‌ی متحرک ۲۸ روزه است؛ برای دیدن اثر کامل حدود یک ماه صبر کنید.

آیا اپلیکیشن‌های تک‌صفحه‌ای Core Web Vitals بدتری دارند؟

لزوماً نه، اما رندر فقط سمت کلاینت اغلب به LCP و باندل‌های بزرگ به INP آسیب می‌زنند. Prerender، Code Splitting و به‌روزرسانی دقیق State بیشتر مشکلات را حل می‌کنند.