Core Web Vitals & Page Performance
Also Known As
TL;DR
Explanation
Core Web Vitals (2024 set): LCP (Largest Contentful Paint, <2.5s good) — time until the largest image or text block renders; INP (Interaction to Next Paint, <200ms good, replaces FID) — responsiveness to user interactions; CLS (Cumulative Layout Shift, <0.1 good) — visual stability, unexpected layout movements. Optimising LCP: serve images in WebP/AVIF, use rel='preload' for hero images, server-side render critical content, use a CDN. Optimising INP: break up long tasks with scheduler.yield(), use web workers for heavy JS. Optimising CLS: always specify width/height on images and iframes, avoid inserting content above existing content. Measure with Lighthouse, PageSpeed Insights, or field data from Chrome UX Report. For PHP applications, server response time (TTFB) directly affects LCP.
Common Misconception
Why It Matters
Common Mistakes
- Render-blocking JavaScript in <head> without async or defer attributes.
- Large unoptimised images — the single biggest contributor to page weight.
- No lazy loading for below-the-fold images — loading=lazy is a one-attribute fix.
- Not measuring with real users (RUM) — synthetic tests miss real-world network and device conditions.
Code Examples
<!-- Render-blocking resources in <head>: -->
<head>
<script src="large-bundle.js"></script> <!-- Blocks rendering -->
<link rel="stylesheet" href="all-styles.css"> <!-- Blocks rendering -->
</head>
<!-- Non-blocking: -->
<script src="large-bundle.js" defer></script>
<link rel="preload" href="critical.css" as="style">
<!-- Preload critical resources -->
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
<link rel="preload" as="font" href="/fonts/sora.woff2" crossorigin>
<!-- Lazy-load below-fold images -->
<img src="product.webp" loading="lazy" decoding="async"
width="800" height="600" alt="Product photo">
<!-- Avoid layout shift — always specify dimensions -->
<img width="1200" height="630" src="banner.webp" alt="">
<!-- Defer non-critical JS -->
<script src="analytics.js" defer></script>
<script src="chatwidget.js" async></script>
<!-- Reduce INP: break up long tasks -->
// In JS:
await scheduler.yield(); // hand control back to browser between chunks