مقدمه: چرا LCP مهمترین معیار Core Web Vitals است؟
بیاید رک باشیم: وقتی کاربر وارد یه سایت میشه، اولین چیزی که بهش اهمیت میده اینه که محتوای اصلی صفحه چقدر سریع جلوی چشمش ظاهر بشه. نه اینکه فونتها چقدر زیبان، نه اینکه انیمیشنها چقدر نرمن — فقط اینکه ببینه صفحه بارگذاری شده و میتونه ازش استفاده کنه.
دقیقاً همین چیزیه که LCP (Largest Contentful Paint) اندازه میگیره.
LCP زمانی رو ثبت میکنه که بزرگترین المان محتوایی قابلمشاهده در viewport — معمولاً یه تصویر hero، ویدیو، یا بلوک متنی بزرگ — کاملاً رندر بشه. گوگل آستانه ۲.۵ ثانیه رو برای LCP «خوب» تعیین کرده و هر چیزی بالای ۴ ثانیه رو «ضعیف» حساب میکنه.
اگه مقالات قبلی ما رو درباره بهینهسازی INP و بهینهسازی TTFB با Edge Computing خوندید، خبر خوب اینه که با این مقاله سهگانه Core Web Vitals رو کامل میکنید. INP تعاملپذیری رو میسنجه، TTFB سرعت پاسخ سرور رو، و LCP سرعت نمایش محتوای اصلی رو. این سه تا با هم تصویر کاملی از عملکرد سایت شما به گوگل و کاربرانتون میدن.
حالا یه آمار جالب: طبق دادههای CrUX (Chrome User Experience Report) در سال ۲۰۲۵، فقط حدود ۶۶ درصد سایتها LCP خوبی دارن و تنها ۴۷ درصد سایتها تمام آستانههای Core Web Vitals رو رد میکنن. یعنی بیش از نصف وب هنوز جای بهبود جدی داره. و اگه سایت شما جزو اون ۳۴ درصد با LCP ضعیفه، صادقانه بگم — دارید هم کاربر و هم رتبه سئو از دست میدید.
آناتومی LCP: چهار زیرمجموعهای که باید بشناسید
برای بهینهسازی مؤثر LCP، اول باید بدونید این معیار از چه اجزایی ساخته شده. خبر خوب اینه که از فوریه ۲۰۲۵، گوگل دادههای زیرمجموعههای LCP رو هم در CrUX منتشر میکنه و تحلیل دقیقتر مشکلات رو خیلی راحتتر کرده.
۱. زمان تا اولین بایت (TTFB)
این فاز از لحظه شروع بارگذاری صفحه تا دریافت اولین بایت پاسخ HTML از سرور رو شامل میشه. اگه مقاله ما درباره TTFB و Edge Computing رو خوندید، میدونید که این زمان شامل DNS lookup، TCP/TLS handshake و پردازش سرور میشه.
نکتهای که خیلیها بهش توجه نمیکنن: بهینهسازی TTFB مستقیماً LCP رو هم بهتر میکنه. کاهش ۴۰۰ میلیثانیهای TTFB تقریباً همون مقدار از LCP هم کم میکنه. یعنی یه تیر و دو نشان!
۲. تأخیر بارگذاری منبع (Resource Load Delay)
فاصله زمانی بین دریافت اولین بایت HTML و شروع بارگذاری منبع LCP (مثلاً تصویر hero). این تأخیر معمولاً به خاطر اینه که مرورگر باید اول HTML رو parse کنه، CSSها رو دانلود و پردازش کنه و بعد تازه متوجه بشه که تصویر LCP رو باید بارگذاری کنه.
این فاز معمولاً بزرگترین فرصت بهینهسازی رو داره. من توی تجربه خودم هم دیدم که بیشتر بهبودهای چشمگیر LCP، دقیقاً از کاهش همین تأخیر بوده.
۳. مدت بارگذاری منبع (Resource Load Duration)
زمان واقعی دانلود منبع LCP. خب اگه تصویر hero شما ۲ مگابایت باشه روی شبکه 3G، طبیعتاً این فاز طولانی میشه. فشردهسازی تصاویر، استفاده از فرمتهای مدرن و CDN مستقیماً روی این فاز تأثیر میذارن.
۴. تأخیر رندر المان (Element Render Delay)
فاصله بین اتمام دانلود منبع LCP و رندر واقعی اون المان. این تأخیر ممکنه به خاطر render-blocking CSS، فونتهایی که هنوز بارگذاری نشدن، یا JavaScript که رندر رو مسدود کرده اتفاق بیفته.
یه نکته مهم: اگه المان LCP یه بلوک متنی باشه (مثل عنوان H1)، فقط فازهای ۱ و ۴ اعمال میشن — چون متن نیاز به دانلود منبع خارجی نداره. ولی اگه المان LCP تصویر باشه (که صادقانه در اکثر سایتها اینطوریه)، هر چهار فاز درگیرن.
المان LCP شما چیه؟ اول تشخیص بدید
قبل از هر بهینهسازی، باید بدونید المان LCP صفحهتون دقیقاً چیه. باورتون نمیشه ولی خیلی از توسعهدهندهها فرض میکنن المان LCP رو میدونن، و وقتی واقعاً چک میکنن متوجه میشن اشتباه فکر میکردن!
انواع المانهای LCP
طبق تعریف گوگل، المانهای زیر کاندیدای LCP هستن:
- تصاویر (
<img>) - تصاویر داخل
<svg> - تصاویر پوستر ویدیو (
<video poster>) - المانهایی با تصویر پسزمینه CSS (از طریق
url()) - المانهای متنی بلوکی مثل
<h1>،<p>،<div>
شناسایی با Chrome DevTools
سادهترین راه برای شناسایی المان LCP، استفاده از تب Performance در Chrome DevTools هست:
- DevTools رو باز کنید (F12)
- به تب Performance برید
- ضبط رو شروع کنید و صفحه رو رفرش کنید
- در نوار Timings، دنبال مارکر «LCP» بگردید
- روش کلیک کنید تا المان LCP هایلایت بشه
یه راه دیگه هم استفاده از Lighthouse هست — در بخش Performance، المان LCP رو بهتون نشون میده و حتی میگه کدوم فاز بیشترین سهم رو در تأخیر داره. خیلی کاربردیه.
شناسایی برنامهنویسی با JavaScript
// Identify the LCP element programmatically
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP element:', lastEntry.element);
console.log('LCP time:', lastEntry.startTime, 'ms');
console.log('LCP size:', lastEntry.size);
console.log('LCP url:', lastEntry.url); // For images
console.log('LCP render time:', lastEntry.renderTime);
console.log('LCP load time:', lastEntry.loadTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
این کد المان LCP رو به همراه زمانبندی دقیقش در کنسول نمایش میده. مقادیر renderTime و loadTime بهتون کمک میکنن بفهمید مشکل در بارگذاری منبعه یا در رندر کردنش — که برای دیباگ کردن طلاست.
تکنیک ۱: اولویتبندی بارگذاری منبع LCP
یکی از رایجترین دلایل LCP کند، دیر کشف شدن منبع LCP توسط مرورگره. تصور کنید تصویر hero شما از طریق CSS background-image تعریف شده — مرورگر اول باید HTML رو parse کنه، بعد CSS رو دانلود و parse کنه، و تازه اونوقت میفهمه باید تصویر رو هم دانلود کنه. این زنجیره وابستگی، تأخیر بارگذاری منبع رو به شدت افزایش میده.
Preload: کشف زودهنگام منبع LCP
با استفاده از <link rel="preload">، به مرورگر میگید که این منبع رو خیلی زود شروع به دانلود کنه، حتی قبل از اینکه بهطور طبیعی کشفش کنه:
<!-- Preload the LCP image -->
<link
rel="preload"
as="image"
href="/images/hero-banner.webp"
type="image/webp"
fetchpriority="high"
>
<!-- For responsive images with srcset -->
<link
rel="preload"
as="image"
imagesrcset="
/images/hero-400.webp 400w,
/images/hero-800.webp 800w,
/images/hero-1200.webp 1200w
"
imagesizes="100vw"
fetchpriority="high"
>
fetchpriority: بالا بردن اولویت دانلود
ویژگی fetchpriority به مرورگر میگه که یه منبع خاص رو با اولویت بالا دانلود کنه. و اینجا اعداد واقعاً حرف میزنن — طبق آزمایشهای خود گوگل، اضافه کردن fetchpriority="high" به تصویر LCP تونسته LCP رو از ۲.۶ ثانیه به ۱.۹ ثانیه کاهش بده. یعنی تقریباً ۷۰۰ میلیثانیه بهبود فقط با یه attribute!
<!-- Mark the LCP image with high fetch priority -->
<img
src="/images/hero-banner.webp"
alt="بنر اصلی سایت"
width="1200"
height="600"
fetchpriority="high"
>
<!-- Important: Do NOT lazy-load the LCP image! -->
<!-- This is WRONG: -->
<img
src="/images/hero-banner.webp"
loading="lazy"
fetchpriority="high"
>
خطای رایج: هرگز loading="lazy" رو روی تصویر LCP نذارید! Lazy loading باعث میشه بارگذاری تصویر تا زمانی که به viewport نزدیک بشه به تعویق بیفته — دقیقاً عکس چیزی که میخواید. تصویر LCP باید هر چه سریعتر بارگذاری بشه. این اشتباه رو خیلیها مرتکب میشن، پس حواستون باشه.
تصاویر پسزمینه CSS: یه مشکل پنهان
اگه المان LCP شما از background-image در CSS استفاده میکنه، مرورگر دیرتر اون رو کشف میکنه. بهترین کار اینه که تصویر رو به تگ <img> تغییر بدید، یا حداقل از preload استفاده کنید:
<!-- Convert CSS background to img tag -->
<!-- Before: discovered late -->
<style>
.hero { background-image: url('/images/hero.webp'); }
</style>
<div class="hero"></div>
<!-- After: discovered immediately from HTML -->
<img
class="hero-img"
src="/images/hero.webp"
alt="Hero image"
fetchpriority="high"
width="1200"
height="600"
>
تکنیک ۲: بهینهسازی تصاویر با فرمتهای مدرن
خب بریم سراغ یکی از تأثیرگذارترین بخشها. در سال ۲۰۲۶، استفاده از JPEG و PNG بدون فشردهسازی مدرن دیگه واقعاً قابلتوجیه نیست. فرمتهای جدید میتونن حجم تصاویر رو ۳۰ تا ۷۰ درصد کاهش بدن بدون افت کیفیت محسوس — و این عدد واقعاً بزرگه.
مقایسه فرمتهای تصویر
بیاید ببینیم هر فرمت چه مزایا و معایبی داره:
- JPEG: فرمت کلاسیک با پشتیبانی جهانی. فشردهسازی خوب ولی بدون شفافیت. مبنای مقایسه (۱۰۰٪ حجم)
- WebP: ۲۵ تا ۳۴ درصد کوچکتر از JPEG با کیفیت مشابه. پشتیبانی از شفافیت و انیمیشن. پشتیبانی مرورگرها در ۲۰۲۶ دیگه کامله
- AVIF: تا ۵۰ درصد کوچکتر از JPEG! بهترین نسبت فشردهسازی به کیفیت. پشتیبانی مرورگرها تقریباً کامل شده، ولی encode کردنش کندتره (یه trade-off که ارزشش رو داره)
پیادهسازی با تگ picture
بهترین استراتژی اینه که AVIF رو اول ارائه بدید، WebP به عنوان fallback، و JPEG به عنوان آخرین گزینه:
<picture>
<!-- AVIF: best compression, serve first -->
<source
srcset="
/images/hero-400.avif 400w,
/images/hero-800.avif 800w,
/images/hero-1200.avif 1200w
"
sizes="(max-width: 768px) 100vw, 80vw"
type="image/avif"
>
<!-- WebP: good compression, wide support -->
<source
srcset="
/images/hero-400.webp 400w,
/images/hero-800.webp 800w,
/images/hero-1200.webp 1200w
"
sizes="(max-width: 768px) 100vw, 80vw"
type="image/webp"
>
<!-- JPEG: universal fallback -->
<img
srcset="
/images/hero-400.jpg 400w,
/images/hero-800.jpg 800w,
/images/hero-1200.jpg 1200w
"
sizes="(max-width: 768px) 100vw, 80vw"
src="/images/hero-800.jpg"
alt="تصویر اصلی صفحه"
width="1200"
height="600"
fetchpriority="high"
>
</picture>
خودکارسازی تبدیل فرمت تصاویر
برای پروژههایی با تعداد زیاد تصاویر، تبدیل دستی فرمت عملی نیست. این اسکریپت ساده با Sharp (کتابخانه Node.js) تصاویر رو بهصورت خودکار تبدیل و بهینه میکنه:
// image-optimizer.mjs
import sharp from 'sharp';
import { readdir } from 'fs/promises';
import { join, parse } from 'path';
const INPUT_DIR = './images/originals';
const OUTPUT_DIR = './images/optimized';
const WIDTHS = [400, 800, 1200, 1600];
async function optimizeImages() {
const files = await readdir(INPUT_DIR);
const imageFiles = files.filter(f =>
/\.(jpe?g|png)$/i.test(f)
);
for (const file of imageFiles) {
const { name } = parse(file);
const inputPath = join(INPUT_DIR, file);
for (const width of WIDTHS) {
const image = sharp(inputPath).resize(width);
// Generate AVIF
await image
.clone()
.avif({ quality: 50, effort: 6 })
.toFile(join(OUTPUT_DIR, `${name}-${width}.avif`));
// Generate WebP
await image
.clone()
.webp({ quality: 75, effort: 5 })
.toFile(join(OUTPUT_DIR, `${name}-${width}.webp`));
// Generate optimized JPEG
await image
.clone()
.jpeg({ quality: 80, mozjpeg: true })
.toFile(join(OUTPUT_DIR, `${name}-${width}.jpg`));
}
console.log(`Optimized: ${file}`);
}
}
optimizeImages();
یه نکته کاربردی: اگه از CDNهایی مثل Cloudflare یا Imgix استفاده میکنید، لازم نیست خودتون این کار رو بکنید. خود CDN میتونه تبدیل فرمت رو خودکار انجام بده. مثلاً Cloudflare Polish و Image Resizing بهصورت خودکار AVIF و WebP رو بر اساس Accept header مرورگر سرو میکنن. واقعاً زندگی رو راحتتر میکنه.
تکنیک ۳: Responsive Images و جلوگیری از بارگذاری تصاویر بزرگتر از نیاز
یکی از اشتباهات رایجی که همهجا میبینم اینه که یه تصویر ۱۶۰۰ پیکسلی رو به کاربر موبایل با صفحه ۳۷۵ پیکسلی ارسال میکنن. این یعنی ۴ تا ۵ برابر داده اضافی که هم پهنای باند هدر میده و هم LCP رو کند میکنه.
استفاده صحیح از srcset و sizes
<img
srcset="
/images/hero-400.webp 400w,
/images/hero-800.webp 800w,
/images/hero-1200.webp 1200w,
/images/hero-1600.webp 1600w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 80vw,
60vw
"
src="/images/hero-800.webp"
alt="Hero banner"
width="1600"
height="800"
fetchpriority="high"
>
با این تنظیمات، مرورگر بر اساس عرض viewport و تراکم پیکسل صفحهنمایش، مناسبترین سایز تصویر رو انتخاب و دانلود میکنه. نتیجه؟ کاربر موبایل تصویر ۴۰۰ پیکسلی دریافت میکنه (حدود ۳۰ کیلوبایت) بهجای ۱۶۰۰ پیکسلی (حدود ۲۰۰ کیلوبایت). تفاوت قابلتوجهیه.
اهمیت ابعاد width و height
همیشه width و height رو روی تصاویر تنظیم کنید — این رو جدی بگیرید. این ویژگیها به مرورگر اجازه میدن قبل از بارگذاری تصویر، فضای لازم رو رزرو کنه و از Layout Shift جلوگیری بشه. این نهتنها CLS رو بهبود میده، بلکه مستقیماً Element Render Delay رو هم کاهش میده چون مرورگر نیاز به محاسبه مجدد layout نداره.
تکنیک ۴: حذف موانع رندر (Render-Blocking Resources)
حتی اگه منبع LCP سریع دانلود بشه، اگه CSS یا JavaScript رندر صفحه رو مسدود کرده باشه، المان LCP نمایش داده نمیشه. این یکی از اون مشکلاتیه که ممکنه ساعتها وقتتون رو بگیره تا پیداش کنید.
Inline Critical CSS
CSSای که برای رندر محتوای بالای fold لازمه رو مستقیماً در HTML قرار بدید:
<head>
<!-- Inline critical CSS for above-the-fold content -->
<style>
/* Critical CSS: hero section, header, layout */
.header {
display: flex;
align-items: center;
height: 64px;
background: #fff;
}
.hero {
position: relative;
width: 100%;
aspect-ratio: 2/1;
}
.hero-img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* ... minimum CSS needed for initial render */
</style>
<!-- Load the rest of CSS asynchronously -->
<link
rel="preload"
href="/css/main.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
>
<noscript>
<link rel="stylesheet" href="/css/main.css">
</noscript>
</head>
بارگذاری غیرهمزمان JavaScript
اسکریپتهایی که برای رندر اولیه لازم نیستن رو با defer یا async بارگذاری کنید:
<!-- Defer non-critical scripts -->
<script src="/js/analytics.js" defer></script>
<script src="/js/interactivity.js" defer></script>
<!-- Never load heavy scripts synchronously -->
<!-- BAD: blocks rendering -->
<script src="/js/heavy-library.js"></script>
فونتها: دشمن پنهان LCP
اگه المان LCP شما متنیه (مثل عنوان H1)، فونتهای وب میتونن LCP رو به شدت تحت تأثیر قرار بدن. بهطور پیشفرض، مرورگر تا بارگذاری کامل فونت صبر میکنه و متن نامرئی نمایش میده — اصطلاحاً FOIT یا Flash of Invisible Text. و صادقانه بگم، این یکی از آزاردهندهترین مشکلات عملکردیه.
/* Use font-display: swap for web fonts */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-font.woff2') format('woff2');
font-display: swap;
/* swap shows fallback font immediately,
then swaps to custom font when loaded */
}
/* Preload the critical font file */
<!-- In the HTML head -->
<link
rel="preload"
href="/fonts/custom-font.woff2"
as="font"
type="font/woff2"
crossorigin
>
ترکیب font-display: swap با preload فونت، تضمین میکنه که متن فوراً با فونت سیستمی نمایش داده بشه و بعد از بارگذاری فونت وب، جایگزین بشه. نتیجه؟ LCP متنی بهشدت بهبود پیدا میکنه.
تکنیک ۵: بهینهسازی سمت سرور برای LCP
بهینهسازی LCP فقط مربوط به فرانتاند نیست — و این نکتهایه که خیلیها فراموش میکنن. سمت سرور هم نقش خیلی مهمی داره، مخصوصاً در اپلیکیشنهایی که از SSR استفاده میکنن.
HTTP 103 Early Hints
خب، بریم سراغ یکی از جذابترین تکنیکهای جدید. HTTP 103 Early Hints یه پروتکله که بهش اجازه میده سرور قبل از آماده شدن پاسخ اصلی، به مرورگر بگه منابع خاصی رو preload کنه. یعنی در حالی که سرور داره HTML رو تولید میکنه، مرورگر داره تصویر LCP رو دانلود میکنه! عالیه، نه؟
// Node.js/Express example of Early Hints
app.get('/', (req, res) => {
// Send 103 Early Hints before the actual response
res.writeEarlyHints({
link: [
'</images/hero.avif>; rel=preload; as=image; type=image/avif',
'</fonts/main.woff2>; rel=preload; as=font; crossorigin',
'</css/critical.css>; rel=preload; as=style'
]
});
// Continue processing the request...
const html = renderPage(); // This might take 200ms
// Send the actual response
res.status(200).send(html);
});
با Early Hints، مرورگر میتونه ۱۰۰ تا ۵۰۰ میلیثانیه زودتر شروع به دانلود منابع حیاتی کنه. Cloudflare هم از Early Hints پشتیبانی میکنه و میتونید بدون تغییر در کد سرورتون ازش استفاده کنید — یکی از اون بهینهسازیهای «مفت» که نباید ازش بگذرید.
Streaming SSR برای بهبود LCP
بهجای اینکه منتظر بمونید تا کل HTML ساخته بشه و بعد یکجا بفرستیدش، میتونید HTML رو بهصورت استریم به مرورگر بفرستید. فریمورکهای مدرن مثل Next.js، Remix و SvelteKit همه از Streaming SSR پشتیبانی میکنن:
// Next.js App Router: Streaming with Suspense
import { Suspense } from 'react';
export default function Page() {
return (
<main>
{/* Hero section streams immediately */}
<section className="hero">
<img
src="/images/hero.avif"
alt="Hero"
width={1200}
height={600}
priority {/* Next.js: adds fetchpriority="high" */}
/>
<h1>عنوان اصلی صفحه</h1>
</section>
{/* Below-fold content can stream later */}
<Suspense fallback={<div>در حال بارگذاری...</div>}>
<HeavyComponent />
</Suspense>
</main>
);
}
با Streaming SSR، بخش hero صفحه (شامل المان LCP) خیلی سریعتر به مرورگر میرسه و رندر میشه، حتی اگه بقیه صفحه هنوز در حال تولیده. این تکنیک مخصوصاً برای صفحاتی که محتوای داینامیک سنگینی دارن خیلی مؤثره.
تکنیک ۶: Lazy Loading هوشمند — فقط برای تصاویر غیر-LCP
Lazy loading یه تکنیک عالی برای کاهش بار اولیه صفحهست، ولی باید هوشمندانه ازش استفاده کنید. قانون طلایی رو حفظ کنید: هرگز تصویر LCP رو lazy load نکنید.
<!-- LCP image: load immediately with high priority -->
<img
src="/images/hero.webp"
alt="Hero banner"
width="1200"
height="600"
fetchpriority="high"
>
<!-- Below-fold images: lazy load -->
<img
src="/images/product-1.webp"
alt="Product image"
width="400"
height="300"
loading="lazy"
decoding="async"
>
<img
src="/images/product-2.webp"
alt="Product image"
width="400"
height="300"
loading="lazy"
decoding="async"
>
ویژگی decoding="async" هم جالبه — به مرورگر اجازه میده decode کردن تصویر رو در پسزمینه انجام بده و main thread رو آزاد نگه داره. برای تصاویر زیر fold واقعاً مفیده.
Intersection Observer برای Lazy Loading پیشرفته
اگه نیاز به کنترل بیشتری دارید — مثلاً میخواید تصاویر رو کمی قبل از ورود به viewport بارگذاری کنید تا کاربر هرگز تصویر خالی نبینه — از Intersection Observer استفاده کنید:
// Advanced lazy loading with pre-loading margin
const imageObserver = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
},
{
// Start loading 200px before entering viewport
rootMargin: '200px 0px',
threshold: 0.01
}
);
// Observe all lazy images
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
با rootMargin: '200px 0px'، تصاویر ۲۰۰ پیکسل قبل از رسیدن به viewport شروع به بارگذاری میکنن. نتیجهش اینه که کاربر تقریباً هرگز تصویر خالی نمیبینه، در حالی که بار اولیه صفحه هم سبک میمونه. بهترین حالت هر دو دنیا.
تکنیک ۷: بهینهسازی LCP برای SPAها و Soft Navigation
یکی از چالشهای نسبتاً جدید در دنیای وب، اندازهگیری و بهینهسازی LCP در Single Page Applications هست. در SPAها، بعد از بارگذاری اولیه، ناوبری بین صفحات بدون بارگذاری مجدد کامل صفحه انجام میشه (اصطلاحاً Soft Navigation). تا اخیراً، LCP فقط برای بارگذاری اولیه اندازهگیری میشد و باقی navigationها اصلاً در نظر گرفته نمیشدن.
در سال ۲۰۲۶، کروم داره روی پشتیبانی از Soft Navigation Heuristics کار میکنه که LCP رو برای هر ناوبری نرم هم اندازهگیری کنه. ولی فارغ از اینکه گوگل چطور اندازهگیری میکنه، بهینهسازی LCP در SPAها برای تجربه کاربری خیلی مهمه:
// SPA route change: prefetch critical resources
function navigateToPage(url) {
// Start prefetching the hero image for the next page
const link = document.createElement('link');
link.rel = 'prefetch';
link.as = 'image';
link.href = getHeroImageUrl(url);
document.head.appendChild(link);
// Use skeleton loading for instant visual feedback
showSkeleton();
// Fetch page data
fetchPageData(url).then(data => {
renderPage(data);
hideSkeleton();
});
}
// Prefetch on hover for even faster navigation
document.querySelectorAll('a[data-prefetch]').forEach(link => {
link.addEventListener('mouseenter', () => {
navigateToPage.prefetch(link.href);
});
});
با prefetch کردن تصویر hero صفحه بعدی هنگام hover روی لینک، تصویر قبل از کلیک کاربر شروع به دانلود میشه و ناوبری خیلی سریعتر به نظر میرسه. این یکی از اون ترفندهای ظریفیه که کاربر متوجه نمیشه، ولی حسش میکنه.
ابزارهای اندازهگیری و مانیتورینگ LCP
بهینهسازی بدون اندازهگیری مثل رانندگی با چشم بستهست. جدی میگم — بدون داده نمیتونید بفهمید کجا مشکل دارید. این ابزارها رو برای مانیتورینگ مداوم LCP استفاده کنید:
ابزارهای آزمایشگاهی (Lab Tools)
- Chrome DevTools Performance Panel: تحلیل دقیق فازهای LCP با نمای waterfall. محبوبترین ابزار برای دیباگ
- Lighthouse: امتیاز عملکرد و پیشنهادات بهبود. از کروم ۱۲۶ به بعد، زیرمجموعههای LCP رو هم نشون میده
- WebPageTest: تست از لوکیشنهای مختلف با سرعتهای مختلف اینترنت. قابلیت فیلمبرداری از بارگذاری و مقایسه قبل/بعد
ابزارهای میدانی (Field Tools)
- CrUX Dashboard: دادههای واقعی کاربران از گوگل. نمای ماهانه روند LCP سایت شما
- Search Console — Core Web Vitals Report: نمایش URLهایی که LCP ضعیف دارن به تفکیک موبایل و دسکتاپ
- web-vitals Library: کتابخانه جاوااسکریپت گوگل برای اندازهگیری RUM (Real User Monitoring)
پیادهسازی RUM با web-vitals
// Real User Monitoring with web-vitals library
import { onLCP } from 'web-vitals';
onLCP((metric) => {
// Send LCP data to your analytics
const lcpData = {
value: metric.value,
rating: metric.rating, // 'good', 'needs-improvement', 'poor'
element: metric.entries[0]?.element?.tagName,
url: metric.entries[0]?.url,
navigationType: metric.navigationType,
// LCP subparts (available in supporting browsers)
ttfb: performance.getEntriesByType('navigation')[0]
?.responseStart,
};
// Send to your analytics endpoint
navigator.sendBeacon('/api/vitals', JSON.stringify(lcpData));
console.log('LCP:', lcpData);
});
چکلیست نهایی بهینهسازی LCP
خب، بیاید همه چیزهایی که بررسی کردیم رو خلاصه کنیم. این چکلیست رو برای هر صفحهتون مرور کنید:
شناسایی و تشخیص
- المان LCP هر صفحه کلیدی رو شناسایی کنید
- زیرمجموعههای LCP رو تحلیل کنید تا بفهمید گلوگاه کجاست
- هم Lab و هم Field data رو بررسی کنید — هر کدوم داستان متفاوتی میگن
بارگذاری منابع
fetchpriority="high"رو روی المان LCP تنظیم کنید- از
preloadبرای منابع LCP که دیر کشف میشن استفاده کنید - هرگز تصویر LCP رو lazy load نکنید
- تصاویر background-image رو در صورت امکان به تگ
<img>تبدیل کنید
بهینهسازی تصاویر
- از فرمتهای مدرن (AVIF، WebP) استفاده کنید
- تصاویر Responsive با srcset و sizes ارائه بدید
- width و height رو همیشه مشخص کنید
- فشردهسازی مناسب اعمال کنید
رندر و نمایش
- Critical CSS رو inline کنید
- CSS غیرضروری رو بهصورت غیرهمزمان بارگذاری کنید
- جاوااسکریپت render-blocking رو حذف یا defer کنید
- برای فونتهای وب از
font-display: swapاستفاده کنید
سمت سرور
- TTFB رو با CDN و edge computing بهینه کنید
- از HTTP 103 Early Hints استفاده کنید
- Streaming SSR رو برای بخشهای بالای صفحه فعال کنید
- فشردهسازی Brotli یا gzip رو فعال کنید
جمعبندی: LCP، شاخص اصلی تجربه بارگذاری
LCP یکی از سه معیار اصلی Core Web Vitals هست و مستقیماً احساس کاربر از سرعت بارگذاری سایت رو منعکس میکنه. با خوندن این مقاله و دو مقاله قبلی ما درباره INP و TTFB، حالا تصویر کاملی از هر سه معیار Core Web Vitals دارید:
- TTFB: سرور چقدر سریع پاسخ میده — پایه همه چیز
- LCP: محتوای اصلی چقدر سریع نمایش داده میشه — اولین برداشت کاربر
- INP: صفحه چقدر سریع به تعاملات پاسخ میده — تجربه مداوم کاربر
یه آمار که باید توش دقت کنید: فقط ۴۷ درصد سایتها تمام آستانههای Core Web Vitals رو پاس میکنن. با پیادهسازی تکنیکهایی که توی این مقاله بررسی کردیم — از اولویتبندی بارگذاری و فرمتهای مدرن تصاویر گرفته تا Early Hints و Streaming SSR — میتونید سایتتون رو به اون نیمه بالای جدول ببرید.
شروع کنید با شناسایی المان LCP صفحات کلیدیتون. بعد زیرمجموعهها رو تحلیل کنید تا بفهمید گلوگاه کجاست. و بعد یکییکی تکنیکهای مناسب رو اعمال کنید.
به تجربه من، بهبود LCP معمولاً نتایج چشمگیری در کاهش نرخ پرش و افزایش نرخ تبدیل داره. عملکرد وب یه مسیر مداومه، نه یه مقصد — و با مانیتورینگ مداوم و بهبود تدریجی، سایت شما همیشه در بهترین حالتش خواهد بود.