Fix LCP Issues on Concrete CMS (Loading Speed) | OpsBlu Docs

Fix LCP Issues on Concrete CMS (Loading Speed)

Speed up Concrete CMS LCP by optimizing block rendering, enabling full-page caching, and compressing File Manager images.

Largest Contentful Paint (LCP) measures how quickly the main content of your Concrete CMS site loads. Poor LCP directly impacts SEO rankings and conversion rates.

Target: LCP under 2.5 seconds Good: Under 2.5s | Needs Improvement: 2.5-4.0s | Poor: Over 4.0s

For general LCP concepts, see the global LCP guide.

Concrete CMS-Specific LCP Issues

1. Unoptimized Block Images

The most common LCP issue on Concrete CMS sites is large, unoptimized images in blocks.

Problem: Image blocks loading full-resolution images instead of thumbnails.

Diagnosis:

  • Run PageSpeed Insights
  • Check if hero image is flagged as LCP element
  • Look for "Properly size images" warning
  • Check if image thumbnails are being generated

Solutions:

A. Use Concrete CMS Thumbnail System

Concrete CMS automatically generates thumbnails for uploaded images. Ensure your theme uses them:

<?php
// In your block template or theme
$image = $this->controller->get('image'); // File object

if ($image) {
    // Use thumbnail instead of full image
    $thumbnail = $image->getThumbnail('large'); // or custom thumbnail type

    ?>
    <img
        src="<?php echo $thumbnail->src; ?>"
        width="<?php echo $thumbnail->width; ?>"
        height="<?php echo $thumbnail->height; ?>"
        alt="<?php echo $image->getTitle(); ?>"
        loading="eager"
    >
    <?php
}
?>

B. Define Custom Thumbnail Types

Create custom thumbnail sizes for your needs:

Edit /application/config/concrete.php or create /application/config/generated_overrides/concrete.php:

return [
    'thumbnails' => [
        'types' => [
            'hero_image' => [
                'width' => 1920,
                'height' => 800,
                'crop' => false,
                'quality' => 80
            ],
            'content_image' => [
                'width' => 1200,
                'height' => 675,
                'crop' => false,
                'quality' => 85
            ]
        ]
    ]
];

Then use in templates:

$thumbnail = $image->getThumbnail('hero_image');

C. Implement Responsive Images

Use srcset for responsive images:

<?php
if ($image) {
    $thumb_sm = $image->getThumbnail('small');
    $thumb_md = $image->getThumbnail('medium');
    $thumb_lg = $image->getThumbnail('large');
    ?>
    <img
        src="<?php echo $thumb_lg->src; ?>"
        srcset="<?php echo $thumb_sm->src; ?> 375w,
                <?php echo $thumb_md->src; ?> 768w,
                <?php echo $thumb_lg->src; ?> 1200w"
        sizes="100vw"
        width="<?php echo $thumb_lg->width; ?>"
        height="<?php echo $thumb_lg->height; ?>"
        alt="<?php echo $image->getTitle(); ?>"
        loading="eager"
    >
    <?php
}
?>

D. Enable Native Lazy Loading

For below-fold images:

<img
    src="<?php echo $thumbnail->src; ?>"
    loading="lazy"
    width="<?php echo $thumbnail->width; ?>"
    height="<?php echo $thumbnail->height; ?>"
    alt="<?php echo $image->getTitle(); ?>"
>

Important: Do NOT use loading="lazy" on hero/above-fold images.

2. Theme Asset Optimization

Heavy CSS and JavaScript in themes can delay LCP.

Identify Render-Blocking Resources

Run PageSpeed Insights and look for:

Solutions:

A. Use Concrete CMS Asset System

Properly register and require assets:

In page_theme.php:

<?php
namespace Application\Theme\YourTheme;

use Concrete\Core\Page\Theme\Theme;

class PageTheme extends Theme
{
    public function registerAssets()
    {
        // Register JavaScript with defer
        $this->requireAsset('javascript', 'jquery');
        $this->providesAsset('javascript', 'theme-js');

        // Register CSS
        $this->providesAsset('css', 'theme-css');
    }

    public function getThemeBlockClasses()
    {
        return [];
    }
}

Register assets in elements.php:

return [
    'javascript' => [
        'theme-js' => [
            'file' => 'js/main.js',
            'minify' => true,
            'combine' => true,
            'defer' => true // Add defer attribute
        ]
    ],
    'css' => [
        'theme-css' => [
            'file' => 'css/main.css',
            'minify' => true,
            'combine' => true
        ]
    ]
];

B. Inline Critical CSS

For above-fold styles, inline them in <head>:

<!-- In your page template -->
<style>
/* Critical CSS for hero section */
.hero-section {
    width: 100%;
    min-height: 600px;
    /* ... */
}
</style>

<?php
// Load full CSS asynchronously
$this->requireAsset('css', 'theme-css');
?>

C. Defer Non-Critical JavaScript

Ensure scripts use defer or async:

<?php
// In template
$this->addHeaderItem('<script src="' . $this->getThemePath() . '/js/non-critical.js" defer></script>');
?>

D. Minify and Combine Assets

Enable in Concrete CMS settings:

Dashboard → System & Settings → Optimization → Cache & Speed Settings

  • ✓ Enable CSS Cache
  • ✓ Enable JavaScript Cache
  • ✓ Minify CSS
  • ✓ Minify JavaScript
  • ✓ Combine CSS files
  • ✓ Combine JavaScript files

3. Block Performance

Heavy blocks can significantly impact LCP.

Problematic Blocks:

  • Image sliders/carousels
  • Video backgrounds
  • Complex grid layouts
  • AJAX-loaded content
  • Social media feeds
  • Heavy form blocks

Solutions:

A. Optimize Carousel/Slider Blocks

<?php
// In custom slider block template
// Preload first slide image
if (isset($slides[0])) {
    $firstImage = $slides[0]->getImageFile();
    $thumbnail = $firstImage->getThumbnail('large');
    ?>
    <link rel="preload" as="image" href="<?php echo $thumbnail->src; ?>">
    <?php
}

// Lazy load subsequent slides
foreach ($slides as $index => $slide) {
    $image = $slide->getImageFile();
    $thumbnail = $image->getThumbnail('large');
    ?>
    <img
        src="<?php echo $thumbnail->src; ?>"
        loading="<?php echo $index === 0 ? 'eager' : 'lazy'; ?>"
        width="<?php echo $thumbnail->width; ?>"
        height="<?php echo $thumbnail->height; ?>"
        alt="<?php echo $slide->getTitle(); ?>"
    >
    <?php
}
?>

B. Defer AJAX Content

// Don't load AJAX content until after LCP
window.addEventListener('load', function() {
    // Load AJAX content here
    loadDynamicContent();
});

C. Remove Unused Blocks

  • Audit pages for unused blocks
  • Remove blocks that don't add value
  • Consolidate functionality where possible

4. Marketplace Add-on Overhead

Add-ons are a common source of performance issues.

Audit Add-on Impact

  1. Disable Add-ons One at a Time:

    • Dashboard → System & Settings → Environment → Debug Settings
    • Enable debug mode
    • Test performance with/without specific add-ons
  2. Measure Script Load Time:

    • Use Chrome DevTools Network tab
    • Look for scripts from add-on vendors
    • Check script sizes and load times
  3. Common Problematic Add-ons:

    • Page builder add-ons (can be heavy)
    • Social media integration add-ons
    • Complex form builders
    • Live chat widgets

Solutions:

A. Remove Unused Add-ons

Dashboard → Extend Concrete CMS → Uninstall

Important: Uninstalling removes add-on code. Don't just disable.

B. Optimize Add-on Loading

For add-ons you must keep, defer their scripts:

<?php
// In page template
if (!$c->isEditMode()) {
    ?>
    <script>
    // Delay add-on scripts until after LCP
    window.addEventListener('load', function() {
        // Load add-on scripts here
        const script = document.createElement('script');
        script.src = '/path/to/addon/script.js';
        document.body.appendChild(script);
    });
    </script>
    <?php
}
?>

C. Use Lightweight Alternatives

  • Replace heavy add-ons with lighter alternatives
  • Build custom blocks for specific needs
  • Use native Concrete CMS features when possible

5. Caching Configuration

Proper caching is critical for LCP.

Enable Full Page Caching

Dashboard → System & Settings → Optimization → Cache & Speed Settings

Full Page Caching:

  • ✓ Enable Full Page Caching
  • Set cache lifetime (recommended: 3600 seconds for public pages)
  • Exclude edit mode and dashboard

Block Caching:

  • Enable for static blocks
  • Be careful with dynamic content blocks

Asset Caching:

  • ✓ Enable CSS Cache
  • ✓ Enable JavaScript Cache

Configure Opcache

Edit /etc/php/8.x/fpm/php.ini:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.save_comments=1

Restart PHP-FPM after changes.

Use Redis for Object Caching

Install Redis:

apt-get install redis-server php-redis

Configure Concrete CMS:

Edit /application/config/database.php:

return [
    'default-connection' => 'concrete',
    'connections' => [
        'concrete' => [
            'driver' => 'concrete_pdo_mysql',
            // ... database config
        ]
    ],
    'redis' => [
        'host' => '127.0.0.1',
        'port' => 6379,
        'password' => null,
        'database' => 0
    ]
];

Then enable in Concrete CMS settings.

6. Database Query Optimization

Slow database queries can delay content rendering.

Identify Slow Queries

Enable Query Logging:

Edit /application/config/concrete.php:

return [
    'debug' => [
        'detail' => 'debug',
        'display_errors' => true,
    ],
    'database' => [
        'queries' => [
            'log' => true
        ]
    ]
];

View logs in /application/files/log/queries/

Solutions:

A. Optimize Page Version Queries

Remove old page versions:

Dashboard → System & Settings → Optimization → Automated Jobs

  • Enable "Remove Old Page Versions"
  • Configure to keep last 10 versions
  • Run job

B. Add Database Indexes

For frequently queried custom attributes:

<?php
// In package controller or migration
$db = \Database::connection();
$db->executeQuery('CREATE INDEX idx_custom_attribute ON btCustomTable (attribute_column)');
?>

C. Use Pagination

For page lists:

<?php
$pageList = new \Concrete\Core\Page\PageList();
$pageList->setItemsPerPage(12); // Limit results
$pageList->setNamespace('pagination_namespace');
$paginator = $pageList->getPagination();
?>

7. Font Loading Optimization

Custom fonts can delay LCP if not optimized.

Font Loading Best Practices

A. Preload Fonts

<!-- In page template <head> -->
<link rel="preload" href="<?php echo $this->getThemePath(); ?>/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin>

B. Use Font-Display Swap

@font-face {
    font-family: 'CustomFont';
    src: url('fonts/custom-font.woff2') format('woff2');
    font-display: swap; /* Show fallback immediately */
    font-weight: 400;
    font-style: normal;
}

C. Limit Font Weights

Only load font weights you actually use:

/* Instead of loading 100-900, only load what you need */
@font-face {
    font-family: 'CustomFont';
    src: url('fonts/custom-font-regular.woff2') format('woff2');
    font-weight: 400;
}

@font-face {
    font-family: 'CustomFont';
    src: url('fonts/custom-font-bold.woff2') format('woff2');
    font-weight: 700;
}

8. Server-Side Optimization

Server configuration affects LCP.

Enable Gzip Compression

Apache (.htaccess):

<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/json
</IfModule>

Nginx:

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json;

Enable Browser Caching

Apache (.htaccess):

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    ExpiresByType application/pdf "access plus 1 month"
</IfModule>

Use CDN

Configure CDN for static assets:

Dashboard → System & Settings → Files → File Storage Locations

  • Add CDN location for public files
  • Configure URL mapping
  • Upload existing files to CDN

Testing & Monitoring

Test LCP

Tools:

  1. PageSpeed Insights - Lab and field data
  2. WebPageTest - Detailed waterfall
  3. Chrome DevTools - Local testing (Lighthouse)
  4. GTmetrix - Detailed reports

Test Multiple Pages:

  • Homepage
  • Blog posts
  • Landing pages
  • Form pages

Test Different Devices:

  • Desktop
  • Mobile
  • Tablet

Monitor LCP Over Time

Chrome User Experience Report (CrUX):

  • Real user data in PageSpeed Insights
  • Track trends over 28 days

Google Search Console:

Quick Wins Checklist

Start here for immediate LCP improvements:

  • Use Concrete CMS thumbnail system for all images
  • Add explicit width/height to hero images
  • Use loading="eager" on LCP image
  • Enable full page caching
  • Enable asset minification and combining
  • Uninstall unused marketplace add-ons
  • Use font-display: swap for custom fonts
  • Enable Gzip compression
  • Configure opcache properly
  • Remove old page versions
  • Test with PageSpeed Insights

When to Hire a Developer

Consider hiring a Concrete CMS developer if:

  • LCP consistently over 4 seconds after optimizations
  • Complex custom theme needs optimization
  • Multiple add-ons required but causing performance issues
  • Need custom caching solution
  • Database queries need optimization
  • Server configuration needs expert tuning

Find Developers: Concrete CMS Marketplace

Next Steps

For general LCP optimization strategies, see LCP Optimization Guide.