← Back to knowledge base |
  • Xperience by Kentico

Optimizing LCP Core Web Vitals metric in Xperience by Kentico widgets

In this article, I explore the challenges of optimizing the Largest Contentful Paint (LCP) Core Web Vitals metric in Xperience by Kentico widgets, particularly focusing on how to ensure the first image loads with high priority. Follow along as I share practical steps, implementation tips, and key considerations for achieving a faster loading time and a better LCP score.

🔥 The motivation

Lately, I have been working on optimizing the performance of a website built with Xperience by Kentico. My primary focus has been on improving the Core Web Vitals metrics, particularly the Largest Contentful Paint (LCP). In simple terms, LCP measures the time it takes to render the largest visible element during a page load. This is often an image, which was the case for my website as well. The recommended time for a good LCP is under 2.5 seconds, but my test cases were consistently exceeding this threshold.

LCP score metric thresholds

🤔 Understanding the LCP Optimization Steps

To optimize the LCP metric, Google provides a helpful video that outlines four key steps:

  1. Ensure the LCP resource starts loading as soon as possible.
  2. Ensure the LCP element can render as soon as its resource finishes loading.
  3. Reduce the load time of the LCP resource as much as possible without sacrificing quality.
  4. Deliver the initial HTML as quickly as possible.

While these recommendations are general and applicable to any website, I'd like to focus on a specific challenge that arises due to the nature of Xperience by Kentico (XbyK) widgets: ensuring the LCP resource starts loading as soon as possible.

In this article, I'll focus on the first step: making sure the LCP resource, such as an image, begins loading with high priority immediately. I'll demonstrate the problem and its solution using a simplified example.

⚔️ The Challenge with Resource Loading in Xperience by Kentico

In XbyK, imagine pages with a static layout that includes a main navigation bar and a footer. Between these two elements, there is an Editable Area designed to hold page content in the form of sections and widgets, which are fully managed by site editors. 

Sections in Xperience by Kentico

The LCP element for each page is likely to be an image—the first image rendered within any widget placed on the page. To enhance performance, when implementing widgets, I set images to lazy-load, as demonstrated in the code snippet below:

<img src="<image-url>" loading="lazy" width="<image-width>" height="<image-height>" alt="<image-alt>" />

The Problem

The question is: How do I ensure that the first image on a page, regardless of which widget it is placed in, loads as soon as possible with high priority instead of being lazy-loaded, as demonstrated in the code snippet below?

<img src="<image-url>" loading="eager" fetchpriority="high" width="<image-width>" height="<image-height>" alt="<image-alt>" />

This can be a tricky problem because widgets in XbyK are standalone components that are unaware of their surroundings or the order in which they are rendered on the page.

📊 Measuring the LCP Score

Before diving into the solution, let’s see how we can measure the LCP score.

  1. Open Chrome DevTools.
  2. Go to the Performance tab.
  3. Open the Capture Settings and configure the browser to simulate a less performant device with a slower internet connection. My testing settings are:
    • CPU: 6x slowdown
    • Network: Slow 4G
  4. Click the Record and Reload button to capture the performance data.
Measuring setup for Performance tab in DevTools

In the initial unoptimized setup, the LCP resource is the first image on the page (an image of a cake, in this example). Since the image is lazy-loaded, it is assigned a low loading priority, resulting in a poor LCP score of 4.19 seconds. Our goal is to improve this score by prioritizing the loading of the first image and avoiding lazy-loading.

Poor LCP result

💡 The Solution: Prioritizing LCP Resource Loading within Widgets

Our goal is to identify the first image rendered within any widget on the page and ensure that it is loaded immediately with high priority.

High-Level Approach

To achieve this, we can use the following approach:

  1. Create a flag within the context of each request to track whether the first image has been identified.
  2. When rendering an image, check this flag:
    • If the first image has not yet been identified, render the <img> tag with attributes that ensure immediate, high-priority loading. Then, set the flag to indicate that the first image has been found.
    • For all subsequent images, render the <img> tag with lazy loading.

Implementation

We set up the flag in the page controller using the HttpContext.Items dictionary.

using Kentico.Content.Web.Mvc.Routing;
using Microsoft.AspNetCore.Mvc;
...

[assembly: RegisterWebPageRoute(...)]

namespace Project.Controllers;

public class PageController(...) : Controller
{
    private const string LcpElementKey = "LcpElementFound";

    public async Task<IActionResult> Index()
    {
        var model = ...; // Get the page model

        HttpContext.Items[LcpElementKey] = false; // Set the flag

        return View(model);
    }
}

Then, in the view where images are rendered, we implement a check for this flag.

@using Project.Models
@model ImageReusableContentViewModel

@{
        const string LcpElementKey = "LcpElementFound";
        bool isLcpElement = false;
        
        if (Context.Items.TryGetValue(LcpElementKey, out object value) && value is bool lcpFound)
        {
            isLcpElement = !lcpFound;
            if (isLcpElement)
            {
                Context.Items[LcpElementKey] = true;
            }
        }
    }

<img src="@Model.Url" loading="@(isLcpElement ? "eager" : "lazy")" @(isLcpElement ? "fetchpriority=high" : "") width="@Model.Width" height="@Model.Height" alt="@Model.Alt" />

Testing the Results

After implementing this solution, the first image on the page is loaded immediately, while subsequent images are lazy-loaded. When we test the page again, the LCP score improves significantly. In our case, the LCP dropped to 2.16 seconds, which is within the recommended range.

Good LCP score

🧐 Additional Considerations

Here are some additional steps you can take to further optimize the LCP and other Web Vitals metrics in Xperience by Kentico:

  • Script Handling: Xperience by Kentico requires you to include the <page-builder-scripts> tag in your site layout to ensure proper functionality of forms and other features. Unfortunately, that tag resolves into <script> tags Kentico’s custom JavaScript. These are not marked as async or defer, which makes them render-blocking. To minimize their impact, place these script tags as close as possible to the closing </body> tag. In the screenshots, you may have noticed the jQuery library being linked. Previously, it was included as part of the <page-builder-scripts>; however, from version 29.6.0, linking this library is optional.
  • Image Compression: To reduce the load time of the LCP resource, consider compressing your images using modern formats like AVIF or WebP. Also, serve images in appropriate sizes, taking into account the target device's screen resolution and pixel density. I've written another article on this topic that you may find helpful. Also, in version 30.0.0, Kentico introduced a built-in functionality to optimize images during their upload to the CMS.
  • Initial HTML Delivery: The speed at which the initial HTML is delivered (TTFB metric – Time to First Byte) depends heavily on your server-side implementation and the performance of the underlying infrastructure.

⭐ Conclusion

Optimizing LCP can be challenging, especially in environments like Xperience by Kentico where content is dynamically managed. By prioritizing the loading of the first visible image and following the additional tips provided, you can significantly improve your website's LCP score and, ultimately, its overall user experience.

Happy optimizing!

About the author

Milan Lund is a Freelance Web Developer with Kentico Expertise. He specializes in building and maintaining websites in Xperience by Kentico. Milan writes articles based on his project experiences to assist both his future self and other developers.

Find out more
Milan Lund