Sitecore LCP Optimization | Blue Frog Docs

Sitecore LCP Optimization

Optimize Largest Contentful Paint in Sitecore through caching, image optimization, and .NET performance tuning

Sitecore LCP Optimization

General Guide: See LCP (Largest Contentful Paint) for universal concepts and fixes.

Learn how to optimize Largest Contentful Paint (LCP) in Sitecore sites through advanced caching strategies, image optimization, ASP.NET performance tuning, and Sitecore-specific optimizations.

Understanding LCP in Sitecore

Largest Contentful Paint measures how long it takes for the largest content element to render. In Sitecore, this is often affected by:

  • .NET/ASP.NET server-side rendering
  • Sitecore HTML caching configuration
  • Media Library image processing
  • Razor view compilation
  • xDB tracking initialization
  • Component rendering pipeline

LCP Targets

  • Good: < 2.5 seconds
  • Needs Improvement: 2.5 - 4.0 seconds
  • Poor: > 4.0 seconds

Identify LCP Elements

Using Browser DevTools

  1. Open Chrome DevTools (F12)
  2. Go to Performance tab
  3. Click Record and reload page
  4. Stop recording
  5. Look for LCP marker in timeline

Common Sitecore LCP Elements

  • Hero images in Razor layouts
  • Banner images from Media Library
  • Featured content images
  • Product images (Sitecore Commerce)
  • Background images in components

1. Sitecore HTML Caching

Enable HTML Cache

Via Sitecore UI:

  1. Navigate to rendering definition
  2. Check Cacheable option
  3. Set Cache options (VaryByData, VaryByUser, etc.)

Via Configuration:

<!-- App_Config/Include/YourSite/Caching.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <caching>
      <cache name="html" enabled="true">
        <patch:attribute name="maxSize">500MB</patch:attribute>
      </cache>
    </caching>
  </sitecore>
</configuration>

Configure Rendering Cache

Set cache options in rendering definition:

<rendering
    cacheable="true"
    varyByData="true"
    varyByDevice="false"
    varyByLogin="false"
    varyByParm="false"
    varyByQueryString="false"
    varyByUser="false">
    <cacheKey>MyRendering_$(datasource)</cacheKey>
</rendering>

Programmatically in Razor:

@using Sitecore.Mvc.Analytics.Extensions

@{
    Html.Sitecore().CachingPolicy(p =>
    {
        p.VaryByData();
        p.SetCacheable();
    });
}

Clear HTML Cache Strategically

// Clear specific site HTML cache
var site = Sitecore.Configuration.Factory.GetSite("website");
var cache = Sitecore.Caching.CacheManager.GetHtmlCache(site);
cache.Clear();

// Clear all HTML caches
foreach (var site in Sitecore.Configuration.Factory.GetSiteNames())
{
    var siteObj = Sitecore.Configuration.Factory.GetSite(site);
    if (siteObj != null)
    {
        Sitecore.Caching.CacheManager.GetHtmlCache(siteObj).Clear();
    }
}

2. Sitecore Media Library Optimization

Configure Image Processing

In web.config or patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <!-- Enable media caching -->
      <setting name="Media.CacheEnabled" value="true"/>

      <!-- Set max media cache size -->
      <setting name="Media.MaxSizeInCacheKB" value="5120"/>

      <!-- Enable media response caching -->
      <setting name="Media.ResponseCaching.Enabled" value="true"/>
      <setting name="Media.ResponseCaching.MaxAge" value="31536000"/>

      <!-- Image quality settings -->
      <setting name="Media.DefaultJpegQuality" value="80"/>
      <setting name="Media.DefaultPngQuality" value="80"/>
    </settings>
  </sitecore>
</configuration>

Use Responsive Images

Razor implementation:

@using Sitecore.Resources.Media

@{
    var imageItem = (Sitecore.Data.Items.MediaItem)Sitecore.Context.Database.GetItem(Model.Item["Image"]);

    if (imageItem != null)
    {
        var mediaOptions = new MediaUrlBuilderOptions
        {
            Width = 1920,
            Height = 1080,
            DisableMediaCache = false
        };

        var imageUrl = MediaManager.GetMediaUrl(imageItem, mediaOptions);

        <picture>
            <source media="(min-width: 1200px)"
                    srcset="@HashingUtils.ProtectAssetUrl(MediaManager.GetMediaUrl(imageItem, new MediaUrlBuilderOptions { Width = 1920 }))">
            <source media="(min-width: 768px)"
                    srcset="@HashingUtils.ProtectAssetUrl(MediaManager.GetMediaUrl(imageItem, new MediaUrlBuilderOptions { Width = 1200 }))">
            <source media="(min-width: 480px)"
                    srcset="@HashingUtils.ProtectAssetUrl(MediaManager.GetMediaUrl(imageItem, new MediaUrlBuilderOptions { Width = 768 }))">
            <img src="@HashingUtils.ProtectAssetUrl(imageUrl)"
                 alt="@imageItem.Alt"
                 width="1920"
                 height="1080"
                 loading="eager"
                 fetchpriority="high" />
        </picture>
    }
}

Priority Hints for LCP Images

For hero/above-the-fold images:

@{
    var heroImage = (Sitecore.Data.Items.MediaItem)Sitecore.Context.Database.GetItem(Model.Item["Hero Image"]);

    if (heroImage != null)
    {
        var imageUrl = MediaManager.GetMediaUrl(heroImage, new MediaUrlBuilderOptions { Width = 1920, Height = 1080 });

        <img src="@HashingUtils.ProtectAssetUrl(imageUrl)"
             alt="@heroImage.Alt"
             width="1920"
             height="1080"
             loading="eager"
             fetchpriority="high" />
    }
}

For below-the-fold images:

<img src="@imageUrl"
     alt="@alt"
     loading="lazy" />

Media CDN Configuration

Configure CDN for media:

<setting name="Media.MediaLinkServerUrl" value="https://cdn.yoursite.com"/>

Or in Razor:

@{
    var cdnUrl = Sitecore.Configuration.Settings.GetSetting("Media.CDN.Url");
    var mediaUrl = MediaManager.GetMediaUrl(imageItem);
    var fullUrl = $"{cdnUrl}{mediaUrl}";
}

Advanced Image Optimization

Use Sitecore Image Resizer with Quality Settings:

@using Sitecore.Resources.Media

@{
    var heroImage = (Sitecore.Data.Items.MediaItem)Sitecore.Context.Database.GetItem(Model.Item["Hero Image"]);

    if (heroImage != null)
    {
        var mediaOptions = new MediaUrlBuilderOptions
        {
            Width = 1920,
            Height = 1080,
            Quality = 85,
            Format = "jpg",
            AllowStretch = false,
            DisableMediaCache = false,
            IncludeExtension = true
        };

        var imageUrl = MediaManager.GetMediaUrl(heroImage, mediaOptions);

        <img src="@HashingUtils.ProtectAssetUrl(imageUrl)"
             alt="@heroImage.Alt"
             width="1920"
             height="1080"
             loading="eager"
             fetchpriority="high"
             decoding="async" />
    }
}

WebP Format Support:

@{
    var image = (Sitecore.Data.Items.MediaItem)Sitecore.Context.Database.GetItem(Model.Item["Image"]);

    if (image != null)
    {
        var webpUrl = MediaManager.GetMediaUrl(image, new MediaUrlBuilderOptions
        {
            Width = 1920,
            Format = "webp",
            Quality = 80
        });

        var jpgUrl = MediaManager.GetMediaUrl(image, new MediaUrlBuilderOptions
        {
            Width = 1920,
            Format = "jpg",
            Quality = 85
        });

        <picture>
            <source srcset="@HashingUtils.ProtectAssetUrl(webpUrl)" type="image/webp">
            <source srcset="@HashingUtils.ProtectAssetUrl(jpgUrl)" type="image/jpeg">
            <img src="@HashingUtils.ProtectAssetUrl(jpgUrl)"
                 alt="@image.Alt"
                 width="1920"
                 height="1080"
                 loading="eager"
                 fetchpriority="high" />
        </picture>
    }
}

3. ASP.NET Performance Optimization

Enable Output Caching

In web.config:

<system.web>
  <caching>
    <outputCacheSettings>
      <outputCacheProfiles>
        <add name="SitecorePages"
             duration="3600"
             varyByParam="*"
             location="Server" />
      </outputCacheProfiles>
    </outputCacheSettings>
  </caching>
</system.web>

In Razor view:

@{
    if (!Sitecore.Context.PageMode.IsExperienceEditorEditing)
    {
        Response.Cache.SetCacheability(HttpCacheability.Public);
        Response.Cache.SetExpires(DateTime.Now.AddHours(1));
        Response.Cache.SetValidUntilExpires(true);
    }
}

Enable View State Compression

<system.web>
  <pages enableViewStateMac="false"
         viewStateEncryptionMode="Never"
         controlRenderingCompatibilityVersion="4.0">
    <namespaces>
      <add namespace="Sitecore.Mvc"/>
    </namespaces>
  </pages>
</system.web>

Razor View Compilation

Precompile views for production:

<system.web>
  <compilation debug="false"
               targetFramework="4.8"
               batch="true"
               optimizeCompilations="true">
  </compilation>
</system.web>

Use Razor Generator for ahead-of-time compilation:

# Install Razor Generator
Install-Package RazorGenerator.Mvc

4. IIS and .NET Optimization

Enable HTTP Compression

In web.config:

<system.webServer>
  <httpCompression>
    <staticTypes>
      <add mimeType="text/*" enabled="true"/>
      <add mimeType="message/*" enabled="true"/>
      <add mimeType="application/javascript" enabled="true"/>
      <add mimeType="application/json" enabled="true"/>
      <add mimeType="*/*" enabled="false"/>
    </staticTypes>
    <dynamicTypes>
      <add mimeType="text/*" enabled="true"/>
      <add mimeType="message/*" enabled="true"/>
      <add mimeType="application/x-javascript" enabled="true"/>
      <add mimeType="*/*" enabled="false"/>
    </dynamicTypes>
  </httpCompression>
  <urlCompression doStaticCompression="true" doDynamicCompression="true"/>
</system.webServer>

Enable Static Content Caching

<system.webServer>
  <staticContent>
    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00"/>
  </staticContent>
</system.webServer>

HTTP/2 Configuration

Ensure HTTP/2 is enabled in IIS:

  1. Open IIS Manager
  2. Select server node
  3. Open Configuration Editor
  4. Navigate to system.webServer/serverRuntime
  5. Set enabled to true

5. Sitecore-Specific Optimizations

Disable xDB for Performance Pages

For landing pages without personalization:

<setting name="Xdb.Tracking.Enabled" value="false" patch:source="DisableTracking.config"/>

Or conditionally in code:

if (ShouldDisableTracking())
{
    Sitecore.Analytics.Tracker.Current?.Session?.Interaction?.Close();
}

Optimize Component Rendering

Cache expensive operations:

// Cache component data
public ActionResult MyComponent()
{
    var cacheKey = $"MyComponent_{Sitecore.Context.Item.ID}";
    var cache = Sitecore.Caching.CacheManager.GetNamedInstance<object>("MyCustomCache", StringUtil.ParseSizeString("10MB"));

    var data = cache.GetValue(cacheKey);

    if (data == null)
    {
        data = GetExpensiveData();
        cache.Add(cacheKey, data, TimeSpan.FromHours(1));
    }

    return View(data);
}

Limit Database Calls

Bad (Multiple database calls):

@foreach (var child in Model.Item.Children)
{
    var title = child["Title"];
    var description = child["Description"];
    var image = child["Image"];
}

Good (Single call with field caching):

@{
    var children = Model.Item.Children.ToList();

    foreach (var child in children)
    {
        child.Fields.ReadAll(); // Load all fields at once
        var title = child["Title"];
        var description = child["Description"];
        var image = child["Image"];
    }
}

Use Fast Query for Lists

// Fast query (more performant)
var items = Sitecore.Context.Database.SelectItems($"fast:/sitecore/content/Home//*[@@templateid='{templateId}']");

// Better: Use Content Search API
var index = ContentSearchManager.GetIndex("sitecore_web_index");
using (var context = index.CreateSearchContext())
{
    var results = context.GetQueryable<SearchResultItem>()
        .Where(x => x.TemplateId == templateId)
        .ToList();
}

6. Database and Index Optimization

SQL Server Optimization

Enable connection pooling:

<connectionStrings>
  <add name="core"
       connectionString="Data Source=...;Min Pool Size=5;Max Pool Size=100;Pooling=true" />
  <add name="master"
       connectionString="Data Source=...;Min Pool Size=5;Max Pool Size=100;Pooling=true" />
  <add name="web"
       connectionString="Data Source=...;Min Pool Size=5;Max Pool Size=100;Pooling=true" />
</connectionStrings>

Index maintenance:

-- Rebuild indexes regularly
ALTER INDEX ALL ON [Sitecore_Core].[dbo].[Items] REBUILD;
ALTER INDEX ALL ON [Sitecore_Master].[dbo].[Items] REBUILD;
ALTER INDEX ALL ON [Sitecore_Web].[dbo].[Items] REBUILD;

-- Update statistics
UPDATE STATISTICS [Sitecore_Web].[dbo].[Items];

Rebuild Search Indexes

// Rebuild web index for better search performance
var index = ContentSearchManager.GetIndex("sitecore_web_index");
index.Rebuild();

7. CDN Integration

Configure Azure CDN

Step 1: Configure Sitecore for Azure CDN

<!-- App_Config/Include/CDN/AzureCDN.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <!-- Set CDN URL for media -->
      <setting name="Media.MediaLinkServerUrl" value="https://yourcdn.azureedge.net"/>

      <!-- Enable always include server URL -->
      <setting name="Media.AlwaysIncludeServerUrl" value="true"/>

      <!-- Enable media response caching -->
      <setting name="Media.ResponseCaching.Enabled" value="true"/>
      <setting name="Media.ResponseCaching.MaxAge" value="31536000"/>
    </settings>
  </sitecore>
</configuration>

Step 2: Configure Azure CDN Cache Rules

In Azure Portal:

  1. Navigate to your CDN profile
  2. Add caching rules:
    • Path: /~/media/*
    • Cache expiration: 1 year
    • Query string caching: Cache every unique URL

Step 3: Verify CDN Integration

@using Sitecore.Resources.Media

@{
    var image = (Sitecore.Data.Items.MediaItem)Sitecore.Context.Database.GetItem(Model.Item["Image"]);

    if (image != null)
    {
        var imageUrl = MediaManager.GetMediaUrl(image);
        // imageUrl should now include CDN domain
    }
}

Configure CloudFlare CDN

Step 1: CloudFlare Page Rules

Create page rules in CloudFlare dashboard:

  1. Media Files Cache Rule:

    • URL Pattern: *yoursite.com/-/media/*
    • Settings:
      • Cache Level: Cache Everything
      • Edge Cache TTL: 1 year
      • Browser Cache TTL: 1 year
  2. Static Assets Cache Rule:

    • URL Pattern: *yoursite.com/dist/*
    • Settings:
      • Cache Level: Cache Everything
      • Edge Cache TTL: 1 month

Step 2: CloudFlare Performance Settings

Enable these in CloudFlare Dashboard > Speed:

  • Auto Minify (CSS, JS, HTML)
  • Brotli compression
  • Rocket Loader (test carefully with Sitecore)
  • Mirage (image optimization)
  • Polish (image compression)

Step 3: Sitecore Configuration for CloudFlare

<!-- App_Config/Include/CDN/CloudFlare.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <!-- Enable response caching -->
      <setting name="Media.ResponseCaching.Enabled" value="true"/>
      <setting name="Media.ResponseCaching.MaxAge" value="31536000"/>
    </settings>
  </sitecore>
</configuration>

Step 4: Set Cache-Control Headers

In web.config:

<system.webServer>
  <staticContent>
    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00"/>
  </staticContent>

  <httpProtocol>
    <customHeaders>
      <add name="Cache-Control" value="public, max-age=31536000, immutable"/>
    </customHeaders>
  </httpProtocol>
</system.webServer>

Custom CDN Implementation

For custom CDN solutions:

// /Pipelines/HttpRequest/CDNMediaProvider.cs
using Sitecore.Pipelines.HttpRequest;
using Sitecore.Resources.Media;

namespace YourProject.Pipelines.HttpRequest
{
    public class CDNMediaProvider : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            if (Sitecore.Context.PageMode.IsExperienceEditorEditing)
                return;

            // Override media URL generation
            var cdnUrl = Sitecore.Configuration.Settings.GetSetting("CDN.BaseUrl");

            if (!string.IsNullOrEmpty(cdnUrl))
            {
                // Inject CDN URL logic here
            }
        }
    }
}

Register in config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="YourProject.Pipelines.HttpRequest.CDNMediaProvider, YourProject"
                   patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

CDN Purging Strategy

Azure CDN Purge:

// Purge CDN cache when media is updated
using Microsoft.Azure.Management.Cdn;
using Microsoft.Azure.Management.Cdn.Models;

public void PurgeMediaFromCDN(string mediaPath)
{
    var cdnClient = new CdnManagementClient(credentials);
    var purgeParams = new PurgeParameters
    {
        ContentPaths = new List<string> { mediaPath }
    };

    cdnClient.Endpoints.PurgeContent(
        resourceGroupName: "your-resource-group",
        profileName: "your-cdn-profile",
        endpointName: "your-endpoint",
        contentPaths: purgeParams
    );
}

CloudFlare Purge:

// Purge CloudFlare cache
using System.Net.Http;

public async Task PurgeCloudFlareCache(string[] urls)
{
    var client = new HttpClient();
    var apiToken = Sitecore.Configuration.Settings.GetSetting("CloudFlare.ApiToken");
    var zoneId = Sitecore.Configuration.Settings.GetSetting("CloudFlare.ZoneId");

    client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiToken}");

    var content = new StringContent(
        Newtonsoft.Json.JsonConvert.SerializeObject(new { files = urls }),
        System.Text.Encoding.UTF8,
        "application/json"
    );

    await client.PostAsync(
        $"https://api.cloudflare.com/client/v4/zones/{zoneId}/purge_cache",
        content
    );
}

8. Critical CSS Implementation

Extract and inline critical CSS for above-the-fold content:

@* /Views/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html>
<head>
    <style>
        /* Critical CSS - Inline */
        body { margin: 0; font-family: Arial, sans-serif; }
        .header { background: #333; color: #fff; padding: 1rem; }
        .hero { min-height: 500px; background-size: cover; }
        /* Add other critical styles */
    </style>

    @* Load full CSS asynchronously *@
    <link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>

9. Monitoring and Measurement

Application Insights

Configure Application Insights:

<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
  <TelemetryModules>
    <Add Type="Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule"/>
    <Add Type="Microsoft.ApplicationInsights.Web.RequestTrackingTelemetryModule"/>
    <Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule"/>
  </TelemetryModules>
</ApplicationInsights>

Track LCP with Real User Monitoring

// Add to layout
new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const lastEntry = entries[entries.length - 1];
    const lcpValue = Math.round(lastEntry.renderTime || lastEntry.loadTime);

    // Send to analytics
    if (typeof gtag !== 'undefined') {
        gtag('event', 'web_vitals', {
            'metric_name': 'LCP',
            'metric_value': lcpValue,
            'metric_rating': lcpValue < 2500 ? 'good' : 'poor',
            'page_template': '@Sitecore.Context.Item.TemplateName'
        });
    }
}).observe({type: 'largest-contentful-paint', buffered: true});

Testing LCP Improvements

Tools

  1. PageSpeed Insights: https://pagespeed.web.dev/
  2. WebPageTest: https://www.webpagetest.org/
  3. Chrome DevTools: Performance tab
  4. Lighthouse: Built into Chrome DevTools

Sitecore-Specific Testing

  1. Clear all caches:

    • HTML cache
    • Data cache
    • Item cache
    • Media cache
  2. Test in normal mode (not Experience Editor or Preview)

  3. Test published web database (not master)

  4. Monitor with Sitecore tools:

    • /sitecore/admin/stats.aspx
    • /sitecore/admin/cache.aspx

Common Sitecore LCP Issues

Issue Cause Solution
Slow TTFB .NET processing, database Enable caching, optimize queries
Large images Unoptimized Media Library Configure image processing, use CDN
Render-blocking CSS CSS in head Extract critical CSS
Heavy Razor views Complex view logic Cache rendering, optimize code
xDB overhead Analytics tracking Consider disabling for static pages
No HTML cache Cache not configured Enable and configure HTML cache

Checklist

  • HTML cache enabled for all renderings
  • Media response caching enabled
  • Images have explicit dimensions
  • LCP image uses fetchpriority="high"
  • Output caching configured
  • HTTP compression enabled
  • CDN configured for media
  • Critical CSS inlined
  • View compilation optimized
  • Database indexes rebuilt
  • SQL connection pooling enabled
  • Application Insights configured

Next Steps

// SYS.FOOTER