Fix LCP Issues on Expressionengine (Loading Speed) | OpsBlu Docs

Fix LCP Issues on Expressionengine (Loading Speed)

Speed up ExpressionEngine LCP by enabling tag caching, optimizing channel entry queries, and compressing template-loaded images.

General Guide: See Global LCP Guide for universal concepts and fixes.

What is LCP?

Largest Contentful Paint measures when the largest content element becomes visible. Google recommends LCP under 2.5 seconds. ExpressionEngine (EE) uses a tag-based template language with MySQL-backed channels, so LCP depends on tag caching, channel entry query efficiency, and template parsing speed.

ExpressionEngine-Specific LCP Causes

  • Template tag parsing overhead -- EE parses every {exp:...} tag on every uncached request, each potentially triggering database queries
  • Channel entry queries without caching -- {exp:channel:entries} runs SQL queries that can be slow with large datasets and complex relationships
  • File field images unoptimized -- EE's File Manager serves original uploads without automatic resizing or format conversion
  • Add-on script bloat -- third-party add-ons (Wygwam, Channel Images, BrilliantRetail) inject CSS/JS globally
  • No HTTP cache by default -- EE does not set Cache-Control headers without explicit configuration

Fixes

1. Enable EE Tag Caching

EE supports per-tag caching to skip database queries for frequently-used channel entries:

{!-- Cache the hero banner for 1 hour (3600 seconds) --}
{exp:channel:entries channel="homepage" limit="1" cache="yes" refresh="3600"}
  <img
    src="{hero_image}"
    width="1920" height="600"
    alt="{title}"
    loading="eager"
    fetchpriority="high"
  >
{/exp:channel:entries}

Enable template caching at Admin > System Settings > Template Settings:

Template caching: Enabled
Cache refresh interval: 3600

For full-page caching, use EE's static page caching or a Varnish/Nginx proxy:

// In system/user/config/config.php
$config['new_posts_clear_caches'] = 'yes';
$config['cache_driver'] = 'file'; // or 'redis' if available

2. Optimize Channel Entry Queries

Reduce database query time by limiting fields and using search_id caching:

{!-- BAD: Fetches all fields for all entries --}
{exp:channel:entries channel="blog"}
  {title} {body} {hero_image}
{/exp:channel:entries}

{!-- GOOD: Limit fields, paginate, and cache --}
{exp:channel:entries
  channel="blog"
  limit="10"
  disable="categories|member_data|pagination"
  cache="yes"
  refresh="1800"
}
  <article>
    <h2>{title}</h2>
    <img src="{blog_thumbnail}" width="400" height="225" alt="{title}" loading="lazy">
    <p>{blog_excerpt}</p>
  </article>
{/exp:channel:entries}

The disable parameter is critical -- it prevents EE from running extra queries for data you do not use.

3. Optimize Image Fields

EE's File field serves originals. Use CE Image or a custom image manipulation add-on:

{!-- Using CE Image add-on for responsive images --}
{exp:ce_img:pair src="{hero_image}" width="1920" quality="80" }
  <img
    src="{made}"
    srcset="{exp:ce_img:single src='{hero_image}' width='640' quality='80'} 640w,
            {exp:ce_img:single src='{hero_image}' width='1024' quality='80'} 1024w,
            {made} 1920w"
    sizes="100vw"
    width="1920" height="600"
    alt="{title}"
    loading="eager"
  >
{/exp:ce_img:pair}

If not using an add-on, optimize images before upload and use manual srcset:

<img
  src="{hero_image}"
  width="1920" height="600"
  alt="{title}"
  loading="eager"
  fetchpriority="high"
  style="aspect-ratio: 1920/600; width: 100%; height: auto; object-fit: cover;"
>

4. Defer Add-On Scripts

Audit active add-ons at Add-Ons > Extensions and remove unused ones. For essential add-ons:

{!-- Move add-on scripts to bottom of template and defer --}
{!-- In your layout template, before </body> --}
{exp:channel:entries channel="page" limit="1"}
  {if segment_1 == "contact"}
    <script defer src="/themes/third_party/formbuilder/js/form.js"></script>
  {/if}
{/exp:channel:entries}

5. Set HTTP Cache Headers

Add caching headers in your .htaccess or via EE template:

{!-- At the top of your template group --}
{exp:http_header
  content_type="text/html"
  cache_control="public, max-age=3600"
}
# .htaccess
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpeg "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"
</IfModule>

Measuring LCP on ExpressionEngine

  1. EE Debug Output -- enable at Admin > System Settings > Output and Debugging to see query counts and template parse times
  2. Template Debugger -- shows time spent on each tag, helping identify slow {exp:...} calls
  3. PageSpeed Insights -- test homepage, blog listing pages, and individual entries
  4. TTFB check -- if TTFB exceeds 600ms, focus on tag caching and query optimization before frontend work

Analytics Script Impact

EE analytics are typically added via Wygwam content or template partials:

  • Place GTM/GA scripts in a layout partial that loads at the bottom of the page with async
  • If using an EE analytics add-on, verify it outputs async scripts
  • Avoid inline tracking in channel entry templates -- use a single global analytics partial