
Knowing Core Web Vitals to Improve Website Performance.

In the modern web, user experience is paramount. It's no longer enough for a website to simply function; it must be fast, responsive, and visually stable. Google has codified these aspects of user experience into a set of metrics called Core Web Vitals. Understanding them is crucial for SEO, user retention, and conversion rates.
This article provides a deep dive into each Core Vital and other key performance indicators, exploring what they measure, what affects them, and most importantly, how to optimize them using modern web development techniques.
Understanding the Core Web Vitals
Core Web Vitals are a subset of web vitals that apply to all web pages and should be measured by all site owners. They represent the core of user experience on the web. As of 2025, the three pillars are:
- Largest Contentful Paint (LCP): Measures loading performance.
- Interaction to Next Paint (INP): Measures interactivity. (This has replaced First Input Delay - FID).
- Cumulative Layout Shift (CLS): Measures visual stability.
A good score in these metrics can directly lead to higher search engine rankings and a better overall user experience.
Largest Contentful Paint (LCP): The Loading Metric
LCP measures the time it takes for the largest image or text block visible within the viewport to be rendered, relative to when the page first started loading.
- Good: 2.5 seconds or less
- Needs Improvement: Between 2.5 and 4.0 seconds
- Poor: More than 4.0 seconds
To effectively optimize LCP, you must understand its four sub components:
LCP = Time to First Byte (TTFB) + Resource Load Delay + Resource Load Time + Element Render Delay
Key Factors Affecting LCP
-
Time to First Byte (TTFB)
Time to First Byte is the time it takes for a browser to receive the first byte of page content. A slow TTFB directly delays LCP.
- Common Causes:
- Slow Server Response Times
- Optimization: Use a CDN, implement caching strategies, and use Edge Computing for dynamic content.
- Common Causes:
-
Resource Load Delay
This is the "waiting" time between TTFB and when the browser actually starts downloading the LCP resource
- Signals: A high resource load delay means the browser didn't discover the LCP element early enough. The resource is being blocked or is not easily discoverable in the HTML.
- Common Causes:
- The LCP image is loaded via a CSS
background-image
property (which is invisible to the browser's preload scanner). - The LCP image is loaded by JavaScript after the initial HTML has been parsed.
- The LCP image is incorrectly lazy-loaded.
- Other resources (CSS, fonts, scripts) are being prioritized ahead of the LCP image.
- The LCP image is loaded via a CSS
- Optimization:
- Ensure the LCP image is in the initial HTML: Never load your LCP image with JavaScript. Use a standard
<img>
tag. - Preload the LCP image: If the image is discovered late (e.g., from a CSS file), you can give the browser a hint to load it sooner:
<link rel="preload" as="image" href="hero-image.webp">
. - Use
fetchpriority="high"
: Add this attribute to your LCP<img>
tag to tell the browser to download it immediately with high priority. - Avoid CSS background images for LCP: Always use an
<img>
tag for critical content images.
- Ensure the LCP image is in the initial HTML: Never load your LCP image with JavaScript. Use a standard
-
Resource Load Time
This is the actual time it takes to download the LCP resource file itself (e.g., the time to download
hero-image.webp
).- Signals: A high resource load time means the file itself is too large or the user's network connection is slow.
- Common Causes:
- Slow Resource Loading: Large images, videos, or fonts.
- Optimization:
- Compress Images: Use modern formats like WebP or AVIF to drastically reduce file size.
- Use Responsive Images: Use
srcset
andsizes
to serve appropriately sized images for different screen sizes. - Use a CDN: A CDN not only reduces TTFB but also speeds up resource downloads by serving assets from a server physically closer to the user.
-
Element Render Delay
This is the final delay, from when the LCP resource has finished loading to when the element is actually painted on the screen. (Note: this is sometimes mistakenly called "element translation delay").
- Signals: A high render delay means something is blocking the main thread, preventing the browser from drawing the already-downloaded content.
- Common Causes:
- Client-Side Rendering: Applications that rely heavily on JavaScript to render content will have a delayed LCP.
- Render-Blocking Resources: CSS and JavaScript that must be loaded and processed before the main content can be rendered.
- Optimization:
- Defer non-critical CSS and JavaScript: Use the
defer
orasync
attributes on script tags. Split your CSS into critical (for above-the-fold content, which can be inlined) and non-critical. - Minimize main-thread work: Break up long tasks, a key strategy for improving INP that also helps here.
- Defer non-critical CSS and JavaScript: Use the
How to Optimize LCP
Optimize Server Response (TTFB)
The foundation of a fast LCP is a fast server.
- Use a CDN (Content Delivery Network): A CDN caches your site's assets (HTML, CSS, JS, images) in multiple geographical locations. When a user requests your page, the content is served from the server closest to them, dramatically reducing network latency.
- Edge Computing: For dynamic content, edge functions (like Vercel Edge Functions or Cloudflare Workers) can run your server-side logic at the CDN's edge, even closer to the user than your origin server. This is a game-changer for reducing TTFB for personalized pages.
- Use DNS Prefetching: If your site relies on critical resources from other domains (like fonts from Google Fonts or an API), you can tell the browser to resolve the DNS for that domain ahead of time. This saves time on the initial connection. Add it to the of your HTML: .
Rendering Strategy: SSR vs. CSR
This is one of the most critical decisions for LCP.
- Client-Side Rendering (CSR): The server sends a minimal HTML shell and a large JavaScript bundle. The browser then downloads, parses, and executes the JavaScript to fetch data and render the page.
- Problem: This creates a long chain of requests before the user sees anything meaningful.
HTML -> JS -> API Data -> Render
. This is terrible for LCP. - Operation:
- User requests page.
- Server sends
index.html
with<div id="root"></div>
and<script src="app.js"></g>
. - Browser downloads
app.js
. - React/Vue/Angular boots up.
- Component makes a
fetch()
call to/api/data
. - Once data arrives, the component renders the final HTML. The LCP element appears very late.
- Problem: This creates a long chain of requests before the user sees anything meaningful.
- Server-Side Rendering (SSR): The server renders the full HTML of the page, including the content from data fetches, and sends it to the browser.
-
Benefit: The browser receives a complete HTML document and can start rendering immediately. The LCP element is present in the initial payload.
-
Operation:
- User requests page.
- On the Node.js server, the framework (e.g., Next.js, Nuxt) detects the page request.
- It calls a server-side data fetching function (like
getServerSideProps
in Next.js). - The server waits for the data fetch to complete.
- It then renders the React/Vue components into an HTML string with the data included.
- This fully formed HTML is sent to the browser. The LCP element is rendered almost instantly.
-
Optimization: Strongly prefer SSR for content-heavy pages. The initial HTML from the server should contain the LCP element.
-
Image Optimization
Since the LCP element is often an image, this is a critical area.
-
Use Modern Formats: Serve images in next-gen formats like WebP or AVIF. They offer superior compression compared to JPEG and PNG, resulting in smaller file sizes for the same quality.
-
Responsive Images: Use the
<picture>
element or thesrcset
attribute on<img>
tags to serve different image sizes based on the user's viewport. Don't serve a 1920px desktop image to a mobile user.html
<img srcset="image-small.webp 480w, image-medium.webp 800w, image-large.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, 1200px" src="image-large.webp" alt="A descriptive alt text." loading="lazy" width="1200" height="800" >
-
Implement Strategic Loading (Lazy vs. Eager)
How you instruct the browser to load images is critical. Native browser support makes this easy with the
loading
attribute.- Eager Loading (
loading="eager"
or attribute omitted): This is the default behavior. The browser loads the image as soon as the<img>
tag is parsed.- When to use: Always use for critical, above-the-fold images, especially your LCP element. Explicitly setting
loading="eager"
can be a clear signal of intent.
- When to use: Always use for critical, above-the-fold images, especially your LCP element. Explicitly setting
- Lazy Loading (
loading="lazy"
): This defers the loading of an image until the user scrolls and it is about to enter the viewport.- When to use: Use for all images that are below-the-fold. This saves bandwidth and reduces network contention during the initial page load, allowing critical resources to load faster.
- Avoid
loading="lazy"
for above-the-fold images and LCP images. This will severely delay its discovery and harm your LCP score.
- Eager Loading (
-
Prioritize and Size the LCP Image:
- Use
fetchpriority="high"
on your LCP image to tell the browser to download it before other, less critical resources. - Use responsive
srcset
andsizes
attributes, and explicitly tell the browser to download it eagerly and with high priority.
html
<img src="hero-image.webp" alt="A stunning hero image." loading="eager" fetchpriority="high" width="1200" height="800" srcset="hero-small.webp 480w, hero-medium.webp 800w, hero-large.webp 1200w" sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, 1200px" > <img src="secondary-image.webp" alt="An interesting secondary image." loading="lazy" width="600" height="400" >
- Use
Interaction to Next Paint (INP): The Interactivity Metric
INP is a metric that assesses a page's overall responsiveness to user interactions. It measures the time from when a user initiates an interaction (like a click or key press) until the next frame is painted on the screen, reflecting the visual feedback from that interaction.
- Good: 200 milliseconds or less
- Needs Improvement: Between 200ms and 500ms
- Poor: More than 500ms
Key Factors Affecting INP
- Long-Running JavaScript: The biggest culprit. If the main thread is busy executing a complex script, it cannot respond to user input.
- Large DOM Size: A complex DOM tree requires more work to process and render updates.
- Heavy Event Callbacks: Complex logic inside event listeners for
click
,input
, etc.
How to Optimize INP
Modern JavaScript and API Interaction
How you write your JavaScript is key.
-
Break Up Long Tasks: A single, long-running JavaScript task will block the main thread. Use techniques to yield to the main thread so it can handle user input.
jsx
// Bad: A long loop blocks everything function processAllItems(items) { for (let i = 0; i < items.length; i++) { processItem(items[i]); // This could be a heavy operation } } // Good: Yielding to the main thread async function processAllItemsAsync(items) { for (let i = 0; i < items.length; i++) { processItem(items[i]); // After each item, yield to let the browser handle other things (like clicks) if (i % 10 === 0) { // Yield every 10 items await new Promise(resolve => setTimeout(resolve, 0)); } } }
-
Efficient API Interaction: When a user clicks a button to fetch data, provide immediate feedback.
- On Click: Immediately disable the button and show a loading spinner. This is instant visual feedback (a "paint").
- Fetch Data: Use the
fetch
API asynchronously. - On Success/Error: Update the UI with the new data or an error message, and re-enable the button.
Code Splitting, Bundling, and Dynamic Imports
You don't need all your site's JavaScript on the first page load.
-
Bundling: Tools like Vite and Webpack take all your JS files and bundle them into optimized files for the browser. They can create different bundles for different environments:
- Client Bundle: Code that runs in the user's browser.
- Node Bundle: Code for your SSR server.
- Edge Bundle: Optimized, smaller bundle for running on edge functions.
-
Code Splitting: The bundler can split your code into smaller chunks. The initial page loads only the essential JavaScript.
-
Dynamic
import()
: This is the mechanism for loading those chunks on demand. For example, when a user clicks a button to open a modal, you can load the JavaScript for that modal only at that moment.jsx
const openModalButton = document.getElementById('open-modal'); openModalButton.addEventListener('click', async () => { // The code for Modal.js is only downloaded and executed when the button is clicked const { Modal } = await import('./components/Modal.js'); const modal = new Modal(); modal.open(); });
Server Components
Frameworks like React (with Next.js) and Solid (with SolidStart) are pioneering Server Components.
-
Concept: These are components that render exclusively on the server. They can fetch data directly and their code is never sent to the client. They render to an intermediate format that is streamed to the browser.
-
Benefit for INP: This drastically reduces the amount of JavaScript that needs to be downloaded, parsed, and executed on the client. Less client-side JavaScript means a less busy main thread, which directly improves INP.
-
Example (React Server Component):
jsx
// This component runs ONLY on the server. // Its code is not in the client bundle. async function ProductDetails({ id }) { // Direct database access or data fetch on the server const product = await db.products.find(id); return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> {/* This AddToCart button IS a client component */} <AddToCart productId={product.id} /> </div> ); }
In this model, only the
AddToCart
button component's JavaScript is sent to the client, because it's interactive. TheProductDetails
component's code stays on the server.
Cumulative Layout Shift (CLS): The Visual Stability Metric
CLS measures the sum total of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page. A layout shift happens any time a visible element changes its position from one rendered frame to the next.
- Good: 0.1 or less
- Needs Improvement: Between 0.1 and 0.25
- Poor: More than 0.25
Key Factors Affecting CLS
- Images without dimensions: The browser doesn't know how much space to reserve, so when the image loads, it pushes content down.
- Ads, embeds, and iframes without dimensions.
- Dynamically injected content: A banner or newsletter sign-up form that appears and pushes down the content the user was reading.
- Web fonts causing FOIT/FOUT: Flash of Invisible Text or Flash of Unstyled Text can cause text to reflow.
How to Optimize CLS
-
Always Provide Dimensions: For images and videos, always include
width
andheight
attributes.html
<!-- The browser will reserve a 16:9 aspect-ratio box for this image while it loads --> <img src="image.jpg" width="1280" height="720" alt="...">
-
Reserve Space for Dynamic Content: If you know an ad or a component will load, use CSS to create a placeholder element that reserves the exact amount of space.
css
.ad-slot { min-height: 250px; /* The height of the largest possible ad */ background-color: #f0f0f0; }
-
Use CSS
transform
for Animations: When animating elements, prefertransform: translate()
over changing properties liketop
orleft
.transform
does not trigger a layout reflow.css
/* Bad: Causes layout shift */ .element.animated { position: relative; left: 100px; } /* Good: No layout shift */ .element.animated { transform: translateX(100px); }
Other Important Performance Metrics
Alongside the Core Vitals, tools like Google Lighthouse report on several other metrics that are excellent diagnostic tools. Understanding them helps you pinpoint the root causes of poor Core Vital scores.
First Contentful Paint (FCP)
FCP measures the time from when the page starts loading to when any part of the page's content is rendered on the screen. This could be the first piece of text, an image, or a logo. It answers the user's initial question: "Is it working?"
- Good: 1.8 seconds or less
- What it Signals: A slow FCP points to issues with initial connection speed and render-blocking resources. It's a precursor to LCP; you can't have a good LCP without a good FCP.
- How to Improve FCP:
- Reduce Server Response Time (TTFB): Your browser can't paint anything until it receives the HTML document. Optimize your server's logic, database queries, and implement server-level caching. Using a global CDN to cache your HTML and serve it from the edge is the most effective strategy.
- Eliminate Render-Blocking Resources: By default, CSS and JavaScript block rendering.
- For JavaScript: Use the
defer
orasync
attributes on<script>
tags.defer
ensures the script is downloaded in parallel and executed only after the document is parsed.async
downloads in parallel but will execute as soon as it's ready, which can still block rendering.defer
is generally safer for scripts that are not essential for the initial paint. - For CSS: Non-critical CSS for below-the-fold content or other pages can be loaded asynchronously.
- For JavaScript: Use the
- Inline Critical CSS: Identify the absolute minimum CSS required to style the above-the-fold content. Instead of linking to an external stylesheet (which requires a separate network request), embed this "critical CSS" directly into the HTML document within a
<style>
tag in the<head>
. This allows the browser to start rendering the visible part of the page immediately without waiting for any external files. The rest of the CSS can then be loaded asynchronously.
Total Blocking Time (TBT)
TBT measures the total amount of time between FCP and Time to Interactive (TTI) where the main thread was blocked by a "long task" (any script running for more than 50ms). It's the sum of all time over 50ms for each long task.
- Good: 200 milliseconds or less
- What it Signals: TBT is a lab measurement that is highly correlated with INP. A high TBT means there's too much JavaScript execution, parsing, or compiling happening on the main thread, which will make the page feel sluggish and unresponsive to user input.
- How to Improve TBT:
- Break Up Long Tasks: If you have a script that runs for 150ms, it contributes (150 - 50) = 100ms to your TBT. Break this work into smaller chunks of less than 50ms each using
setTimeout
,requestAnimationFrame
, or anasync
function that yields to the main thread. This prevents any single task from blocking user input for too long. - Reduce Unused JavaScript: Use your bundler's code-splitting capabilities to create smaller JavaScript files. Load code on-demand with dynamic
import()
only when a user needs it (e.g., clicking a button to open a complex UI widget). This reduces the amount of JavaScript that needs to be parsed and compiled on the initial page load. - Optimize Third-Party Scripts: Scripts for analytics, ads, or customer support widgets are common causes of high TBT. Audit their impact using performance tools. Load them with
async
ordefer
, and consider if they can be delayed until after the main content has loaded and the user has interacted with the page.
- Break Up Long Tasks: If you have a script that runs for 150ms, it contributes (150 - 50) = 100ms to your TBT. Break this work into smaller chunks of less than 50ms each using
Speed Index
Speed Index (SI) measures how quickly the content of a page is visibly populated above the fold. It's a more holistic metric than LCP or FCP because it captures the entire visual loading experience, not just a single point in time. It's calculated by analyzing a video of the page loading and scoring the visual completeness at different intervals.
- Good: 3.4 seconds or less
- What it Signals: A high Speed Index means the user is staring at a blank or partially loaded screen for too long, even if the final LCP is fast. It indicates a "janky" or slow-to-start loading process.
- How to Improve Speed Index:
- Optimize the Critical Rendering Path: This is the most important factor. Ensure that the minimal set of HTML, CSS, and JavaScript required to render the visible, above-the-fold content is delivered and processed first. This involves inlining critical CSS and deferring all other assets.
- Optimize Font Loading: Large web fonts can block text from rendering, leaving blank spaces and worsening your SI score. Use
font-display: swap;
in your@font-face
rule. This tells the browser to immediately show text with a system font and then "swap" it for your custom font once it has downloaded. Also, preload key font files that are used above the fold. - Prioritize Visible Content: Ensure your image loading strategy supports a fast Speed Index. Images above the fold should be loaded eagerly, while images below the fold should be lazy-loaded. This focuses network bandwidth on rendering the visible part of the page as quickly as possible.
Conclusion
Optimizing for Core Web Vitals is not about ticking a few boxes. It's about adopting a performance-first mindset.
- For Loading Performance (LCP, FCP, SI): Use a CDN and choose the right rendering strategy (SSR for content, SSG for static pages) and caching to improve server response (TTFB). Especially for your LCP element, you must ensure it is discoverable in the initial HTML, optimize the image and load it eagerly with high priority, and compress it to minimize its load time.
- For Interactivity (INP, TBT): A responsive user experience depends on an unblocked main thread. The key is to minimize and manage JavaScript execution. Address high Total Blocking Time (TBT) by breaking long tasks into smaller chunks, aggressively code-splitting your bundles, loading non-essential JavaScript on-demand with dynamic imports, and leveraging server components to keep your client-side bundle lean. Additionally, audit and defer third-party scripts, as they are often the culprits for a sluggish main thread, which directly leads to poor Interaction to Next Paint (INP) scores.
- For Visual Stability (CLS): Eliminate unexpected layout shifts by providing explicit
width
andheight
attributes for all images, videos, and ad slots. This allows the browser to reserve space for content before it loads, creating a stable, predictable, and comfortable viewing experience.
By focusing on these areas, you can build web applications that are not only fast and efficient but also provide a delightful and seamless experience for your users, satisfying both them and the search engines.