Magento LCP Optimization | Blue Frog Docs

Magento LCP Optimization

Fix Largest Contentful Paint (LCP) issues in Magento 2 through caching, image optimization, server configuration, and Varnish implementation.

Magento LCP Optimization

Largest Contentful Paint (LCP) measures how long it takes for the main content of a page to load. For Magento 2 stores, poor LCP often stems from unoptimized images, slow server response times, and inadequate caching. This guide provides comprehensive solutions for achieving LCP under 2.5 seconds.


Understanding LCP in Magento

What is LCP?

Largest Contentful Paint measures the time from when a page starts loading to when the largest visible element (image, video, or text block) is rendered.

Google's LCP Thresholds:

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

Common LCP Elements in Magento

  1. Homepage: Hero banner images, carousel images
  2. Category Pages: Category banner, first product images
  3. Product Pages: Main product image
  4. Checkout: Page header, cart summary

Measuring LCP

1. Chrome DevTools

Steps:

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

2. PageSpeed Insights

Visit: https://pagespeed.web.dev/

  • Enter your Magento URL
  • Check "Field Data" for real-user metrics
  • Check "Lab Data" for simulated results

3. Lighthouse

CLI:

lighthouse https://your-store.com --view

Chrome DevTools:

  1. Open DevTools > Lighthouse tab
  2. Select Performance
  3. Generate report

4. Web Vitals Chrome Extension

Install: Web Vitals Extension

  • Real-time LCP measurement
  • Color-coded indicators
  • Historical data tracking

Full Page Cache (FPC) Optimization

Enable and Configure FPC

1. Enable Built-in FPC:

php bin/magento config:set system/full_page_cache/caching_application 1
php bin/magento cache:enable full_page
php bin/magento cache:flush

2. Configure FPC Settings:

Stores > Configuration > Advanced > System > Full Page Cache
  • Caching Application: Built-in Cache or Varnish
  • TTL for public content: 86400 (1 day)

3. Verify FPC is Working:

curl -I https://your-store.com/ | grep X-Magento-Cache

Expected: X-Magento-Cache-Control: max-age=86400, public

Cache Warming

Pre-populate cache for better LCP on first visit.

Install Cache Warmer:

composer require jeroenvermeulen/magento2-cache-warmer
php bin/magento module:enable JeroenVermeulen_CacheWarmer
php bin/magento setup:upgrade

Configure:

Stores > Configuration > System > Cache Warmer

Manual Cache Warming:

php bin/magento cache:warm

Varnish Configuration

Varnish provides superior caching performance compared to built-in FPC.

Install and Configure Varnish

1. Install Varnish:

# Ubuntu/Debian
apt-get install varnish

# CentOS/RHEL
yum install varnish

2. Generate Varnish VCL:

php bin/magento varnish:vcl:generate --export-version=6 > /etc/varnish/magento.vcl

3. Configure Magento for Varnish:

Stores > Configuration > Advanced > System > Full Page Cache
  • Caching Application: Varnish Cache
  • Backend Host: localhost
  • Backend Port: 8080
  • Grace Period: 300

4. Varnish Configuration:

File: /etc/varnish/default.vcl

vcl 4.0;

import std;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
    .first_byte_timeout = 600s;
    .between_bytes_timeout = 600s;
}

acl purge {
    "localhost";
    "127.0.0.1";
}

sub vcl_recv {
    if (req.method == "PURGE") {
        if (client.ip !~ purge) {
            return (synth(405, "Method not allowed"));
        }
        return (purge);
    }

    if (req.url ~ "^/(pub/)?(media|static)/") {
        # Static files should always be cached
        return (hash);
    }

    if (req.http.Cookie ~ "X-Magento-Vary=") {
        # Vary cookie exists, handle accordingly
        set req.http.X-Magento-Vary = regsub(req.http.Cookie, ".*X-Magento-Vary=([^;]+);*.*", "\1");
    }

    return (hash);
}

sub vcl_backend_response {
    # Cache static content for 1 year
    if (bereq.url ~ "^/(pub/)?(media|static)/") {
        set beresp.ttl = 365d;
        set beresp.http.Cache-Control = "public, max-age=31536000";
    }

    # Set grace period
    set beresp.grace = 5m;
}

sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Magento-Cache-Debug = "HIT";
    } else {
        set resp.http.X-Magento-Cache-Debug = "MISS";
    }
}

5. Start Varnish:

systemctl enable varnish
systemctl start varnish

6. Verify Varnish:

curl -I https://your-store.com/ | grep X-Magento-Cache-Debug

Expected: X-Magento-Cache-Debug: HIT (after second request)

Varnish Performance Tuning

File: /etc/varnish/varnish.params

VARNISH_STORAGE="malloc,4G"  # Increase cache size
VARNISH_TTL=120              # Default TTL in seconds
DAEMON_OPTS="-p thread_pools=4 \
             -p thread_pool_min=100 \
             -p thread_pool_max=5000"

Image Optimization

Images are often the LCP element. Optimize them for faster loading.

1. WebP Conversion

Convert images to WebP format for better compression.

Install WebP Module:

composer require magento/module-page-builder-webp
php bin/magento module:enable Magento_PageBuilderWebp
php bin/magento setup:upgrade

Manual Conversion:

# Install cwebp tool
apt-get install webp

# Convert images
find pub/media/catalog/product -name "*.jpg" -exec bash -c 'cwebp -q 80 "$0" -o "${0%.jpg}.webp"' {} \;

2. Lazy Loading

Prevent off-screen images from blocking LCP.

Native Lazy Loading:

File: view/frontend/templates/product/image.phtml

<img src="<?= $block->getImageUrl() ?>"
     alt="<?= $block->getLabel() ?>"
     loading="lazy"
     width="<?= $block->getWidth() ?>"
     height="<?= $block->getHeight() ?>" />

JavaScript Lazy Loading (for older browsers):

File: view/frontend/templates/lazyload.phtml

<script>
document.addEventListener("DOMContentLoaded", function() {
    var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

    if ("IntersectionObserver" in window) {
        let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    let lazyImage = entry.target;
                    lazyImage.src = lazyImage.dataset.src;
                    lazyImage.classList.remove("lazy");
                    lazyImageObserver.unobserve(lazyImage);
                }
            });
        });

        lazyImages.forEach(function(lazyImage) {
            lazyImageObserver.observe(lazyImage);
        });
    }
});
</script>

3. Image Optimization Tools

Optimize Existing Images:

# Install optimization tools
apt-get install jpegoptim optipng

# Optimize JPG images
find pub/media -name "*.jpg" -exec jpegoptim --strip-all --max=85 {} \;

# Optimize PNG images
find pub/media -name "*.png" -exec optipng -o7 {} \;

Automated Optimization with Magento Extension:

composer require creativestyle/magesuite-image-optimizer
php bin/magento module:enable Creativestyle_ImageOptimizer
php bin/magento setup:upgrade

4. Responsive Images

Serve appropriately sized images for different devices.

File: view/frontend/templates/product/image.phtml

<img src="<?= $block->getImageUrl() ?>"
     srcset="<?= $block->getSmallImageUrl() ?> 480w,
             <?= $block->getMediumImageUrl() ?> 768w,
             <?= $block->getLargeImageUrl() ?> 1200w"
     sizes="(max-width: 480px) 100vw,
            (max-width: 768px) 50vw,
            33vw"
     alt="<?= $block->getLabel() ?>"
     width="1200"
     height="1200" />

5. Preload LCP Image

Preload the hero image to improve LCP.

File: view/frontend/layout/cms_index_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <head>
        <link rel="preload" as="image" href="{{media url='wysiwyg/hero-banner.jpg'}}" />
    </head>
</page>

Dynamic Preload in Template:

<?php if ($block->getHeroImage()): ?>
<link rel="preload" as="image" href="<?= $block->getHeroImage() ?>" />
<?php endif; ?>

Server Optimization

1. Enable HTTP/2

HTTP/2 improves resource loading through multiplexing.

Apache:

File: /etc/apache2/mods-available/http2.conf

<IfModule http2_module>
    Protocols h2 h2c http/1.1
</IfModule>

Enable module:

a2enmod http2
systemctl restart apache2

Nginx:

File: /etc/nginx/sites-available/magento

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    # ... rest of config
}

Reload Nginx:

nginx -t
systemctl reload nginx

2. Enable HTTP/3 (QUIC)

Nginx with QUIC (experimental):

server {
    listen 443 quic reuseport;
    listen 443 ssl http2;

    ssl_protocols TLSv1.3;
    add_header Alt-Svc 'h3=":443"; ma=86400';
}

3. Optimize PHP

Install OPcache:

apt-get install php8.1-opcache

Configure OPcache:

File: /etc/php/8.1/fpm/conf.d/10-opcache.ini

opcache.enable=1
opcache.memory_consumption=512
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=32531
opcache.validate_timestamps=0  # Disable in production
opcache.save_comments=1
opcache.fast_shutdown=1

Restart PHP-FPM:

systemctl restart php8.1-fpm

4. Optimize MySQL

File: /etc/mysql/my.cnf

[mysqld]
innodb_buffer_pool_size = 4G
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
query_cache_size = 64M
query_cache_limit = 2M
table_open_cache = 4000

Restart MySQL:

systemctl restart mysql

Redis Configuration

Redis improves cache and session performance.

Install Redis

apt-get install redis-server php-redis
systemctl enable redis-server
systemctl start redis-server

Configure Magento for Redis

File: app/etc/env.php

'cache' => [
    'frontend' => [
        'default' => [
            'backend' => 'Cm_Cache_Backend_Redis',
            'backend_options' => [
                'server' => '127.0.0.1',
                'port' => '6379',
                'database' => '0',
                'compress_data' => '1'
            ]
        ],
        'page_cache' => [
            'backend' => 'Cm_Cache_Backend_Redis',
            'backend_options' => [
                'server' => '127.0.0.1',
                'port' => '6379',
                'database' => '1',
                'compress_data' => '0'
            ]
        ]
    ]
],
'session' => [
    'save' => 'redis',
    'redis' => [
        'host' => '127.0.0.1',
        'port' => '6379',
        'database' => '2',
        'compression_threshold' => '2048',
        'compression_library' => 'gzip',
        'max_concurrency' => '6',
        'break_after_frontend' => '5',
        'break_after_adminhtml' => '30',
        'first_lifetime' => '600',
        'bot_first_lifetime' => '60',
        'bot_lifetime' => '7200',
        'disable_locking' => '0'
    ]
]

Redis Performance Tuning

File: /etc/redis/redis.conf

maxmemory 2gb
maxmemory-policy allkeys-lru
save ""  # Disable persistence for cache

Restart Redis:

systemctl restart redis-server

CDN Implementation

Use a CDN to serve static assets faster globally.

Configure CDN in Magento

Stores > Configuration > General > Web
  • Base URL for Static View Files: https://cdn.yourstore.com/static/
  • Base URL for User Media Files: https://cdn.yourstore.com/media/
  1. Cloudflare - Free tier available, easy setup
  2. Fastly - Official Magento partner (Adobe Commerce)
  3. AWS CloudFront - AWS integration
  4. KeyCDN - Affordable, good performance
  5. BunnyCDN - Cost-effective option

Critical CSS

Inline critical CSS to prevent render blocking.

Extract Critical CSS

Using Critical CSS Generator:

npm install -g critical

critical https://your-store.com/ \
  --base pub/static/ \
  --inline \
  --minify \
  --width 1300 \
  --height 900 \
  > critical.css

Inline Critical CSS

File: view/frontend/layout/default.xml

<head>
    <style type="text/css">
        /* Inline critical CSS here */
        body{margin:0;padding:0}
        .page-header{background:#000;color:#fff}
        /* ... */
    </style>
</head>

Defer Non-Critical CSS

File: view/frontend/layout/default.xml

<head>
    <css src="css/styles.css" defer="defer"/>
</head>

Font Optimization

Preload Fonts

File: view/frontend/layout/default.xml

<head>
    <link rel="preload" as="font" type="font/woff2"
          href="{{static url='fonts/opensans-regular.woff2'}}"
          crossorigin="anonymous"/>
</head>

Use font-display: swap

File: web/css/source/_typography.less

@font-face {
    font-family: 'Open Sans';
    src: url('../fonts/opensans-regular.woff2') format('woff2');
    font-weight: normal;
    font-style: normal;
    font-display: swap;  /* Prevents invisible text */
}

JavaScript Optimization

Defer JavaScript

File: view/frontend/requirejs-config.js

var config = {
    deps: [],  // Remove synchronous dependencies
    shim: {}
};

Minimize JavaScript Bundling

php bin/magento config:set dev/js/enable_js_bundling 0
php bin/magento config:set dev/js/minify_files 1
php bin/magento config:set dev/js/merge_files 0

Testing & Validation

Before/After Comparison

Test with WebPageTest:

  1. Visit webpagetest.org
  2. Enter URL
  3. Select test location
  4. Run test
  5. Compare LCP metrics

Continuous Monitoring

Google Search Console:

  • Monitor Core Web Vitals report
  • Track field data from real users
  • Identify problematic URLs

Chrome User Experience Report (CrUX):

# Install CrUX API tool
npm install -g crux

# Query your site
crux https://your-store.com/

Quick Wins Checklist

  • Enable Full Page Cache (FPC)
  • Implement Varnish caching
  • Configure Redis for cache and sessions
  • Enable HTTP/2
  • Optimize and compress images
  • Implement lazy loading for images
  • Preload LCP image
  • Use WebP image format
  • Enable OPcache for PHP
  • Configure CDN for static assets
  • Inline critical CSS
  • Optimize font loading
  • Minimize JavaScript blocking

Next Steps


Additional Resources

// SYS.FOOTER