← Back to blog

Magento 2 Core Web Vitals: How to Fix LCP, INP and CLS

Rodrigo Biazzi

Core Web Vitals are the three page experience metrics Google reports from real Chrome users: Largest Contentful Paint (LCP), Interaction to Next Paint (INP) and Cumulative Layout Shift (CLS). Luma based Magento stores tend to struggle with all three on mobile, for reasons that are fairly predictable once you know where to look.

This article goes through each metric, the usual Magento 2.4.8 causes, and the fixes that I check first. It focuses on Luma, since Hyvä stores start from a much better baseline.

Lab data vs field data

Before changing anything, be clear about which numbers you are looking at.

  • Field data comes from the Chrome User Experience Report. It is what Google uses, it is shown at the top of PageSpeed Insights and in the Search Console Core Web Vitals report, and it is measured at the 75th percentile of real visits over 28 days.
  • Lab data comes from a single Lighthouse run on a simulated device. It is useful for debugging but it is not the score that counts.

The thresholds for a good result are LCP at or under 2.5 seconds, INP at or under 200 milliseconds and CLS at or under 0.1. Because field data lags by four weeks, a fix you ship today shows up gradually. Do not judge a change by the next day's report.

Measure per page type. A Magento store is really four or five different sites from a performance point of view: home, category, product, cart and checkout. Search Console groups URLs with similar issues, which usually maps to those page types.

LCP: Largest Contentful Paint

LCP measures when the largest visible element finishes rendering, usually the hero banner on the home page, the first product images on a category page and the main product image on a product page. On Magento, LCP is the sum of four parts, and you need to know which one dominates.

1. Server response time

Nothing renders until the HTML arrives. If time to first byte is high, look at the server before the frontend. Category and product pages should come from full page cache. Check the response headers for a cache hit, and check that no block on the page uses cacheable="false", which disables caching for the whole page.

The Varnish full page cache article covers setup and hit rates. Uncached pages such as the cart are a separate server side tuning job.

2. Render blocking CSS

Luma loads styles-m.css and styles-l.css in the head. They are large and they block rendering. Magento can inline critical CSS and load the rest asynchronously:

bin/magento config:set dev/css/use_css_critical_path 1

This expects a web/css/critical.css file in your theme. Magento does not generate it for you. Generate it with a critical CSS tool against your real templates and keep it small. Enabling the setting without a good critical CSS file causes a flash of unstyled content, which can hurt CLS.

3. Fonts

Web fonts delay text rendering. Preload the one or two font files used above the fold and use font-display: swap in your @font-face rules. In layout XML, the head supports a font element that outputs a preload link:

<head>
    <font src="fonts/inter/inter-regular.woff2"/>
</head>

Check the head for font preloads inherited from the parent theme, such as Open Sans, and remove the ones your theme does not use. A preloaded font is downloaded whether the CSS uses it or not.

4. The LCP image itself

On product pages, the main image is rendered by the Fotorama gallery JavaScript. Until the gallery initialises, Luma shows a placeholder, so the real image appears late. Options, from simplest to most work:

  • Make sure the image served is the right size for the slot and uses a modern format. The product images optimization article covers sizes and WebP.
  • Render the first gallery image as a plain img in the template, so it is in the HTML from the start, and let the gallery take over after.
  • Replace Fotorama with a lighter gallery.

For home page banners and the first row of category products, do not lazy load the images above the fold. Lazy loading the LCP image delays it. Add fetchpriority="high" to the single image that is the LCP element, and keep loading="lazy" for everything below it.

INP: Interaction to Next Paint

INP replaced First Input Delay as a Core Web Vital in March 2024. It measures how long the page takes to respond visually to clicks, taps and key presses, across the whole visit. On Luma, poor INP comes from the main thread being busy.

Too much JavaScript running at load

RequireJS, jQuery, Knockout and the UI component framework all execute on every page. Then each module initialises its widgets. While that runs, a tap on the menu or a swatch waits. The fixes are to load less and run less:

  • Disable modules you do not use. An enabled module adds its JavaScript through layout files even when nobody uses the feature, so check what the page loads, not just the feature list.
  • Bundle by page type instead of loading a long chain of files. See the JavaScript bundling article.
  • Initialise heavy widgets only when they become visible or when the user interacts with them.

Third party scripts

Chat widgets, review widgets, tag managers, heatmaps and A/B testing tools are often the largest INP problem, and they are the hardest to fix because they belong to someone else. Make a list of every third party script, its owner and its purpose. Remove what nobody uses. Load the rest after the page is interactive or after the first interaction, where the vendor allows it. For tracking, a clean GTM setup with fewer tags helps more than any Magento change. The GA4 and GTM tracking article covers how to keep that lean.

Expensive event handlers

Swatch clicks on configurable products recalculate prices, images and stock in JavaScript. With many options, that can take long enough to show up in INP. Use the Performance panel in Chrome DevTools, record a tap on a swatch with CPU throttling, and look at which functions take the time.

CLS: Cumulative Layout Shift

CLS measures content moving after it is shown. Magento has a few classic sources:

  • Images without dimensions. Every img needs width and height attributes, or a CSS aspect ratio, so the browser reserves the space. Check custom templates and CMS content, which often lack them.
  • Content injected by JavaScript. Customer data sections fill the header links, minicart counter and messages after load. Give those containers a fixed size so filling them does not push the page down.
  • Late styled components. Sliders and carousels that render as a stacked list until their JavaScript runs, then collapse into one row. Style the initial state to match the final one.
  • Banners and notices at the top. Cookie bars, promo strips and app banners that appear above the content after load. Overlay them or reserve their space.
  • Web fonts. A large difference between the fallback font and the web font shifts text when the font loads. Choose a close fallback or adjust it with size-adjust in the @font-face rule.

In DevTools, the Performance panel marks layout shifts and the elements that moved. Start with the largest one on mobile.

A workable order

  1. Confirm full page cache hits on catalog pages and fix server response time.
  2. Audit third party scripts and remove or delay what you can.
  3. Fix image dimensions and the LCP image on each page type.
  4. Reserve space for anything injected after load.
  5. Work on CSS delivery and fonts.
  6. Reduce and bundle Luma JavaScript.

Each step is measurable on its own. Ship them one at a time so you can tell which change moved which metric.

FAQ

Can a Luma store pass Core Web Vitals?

Yes, but it takes deliberate work on caching, images, third party scripts and JavaScript. Out of the box, Luma usually struggles on mobile, especially with INP on product pages.

Why is my PageSpeed score good but Search Console still shows poor URLs?

The score is lab data from one simulated run. Search Console uses field data from real visitors over 28 days at the 75th percentile, which includes slow phones, slow networks and third party scripts that a lab run may not trigger.

Does Varnish fix Core Web Vitals?

It fixes the server part of LCP on cacheable pages. It does nothing for JavaScript execution, layout shifts or the time images take to load.

Should I lazy load all images?

No. Lazy load images below the fold only. The LCP image and other images visible on first load should load eagerly, and the LCP image can get fetchpriority set to high.

I am available for freelance Magento and Adobe Commerce work, including Core Web Vitals audits and fixes. If you need help with this, get in touch.

Related articles