Largest Contentful Paint (LCP) measures how quickly the main content of your MODX 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.
MODX-Specific LCP Issues
1. Template Parsing and Rendering Time
MODX must parse templates, process tags, and execute snippets before sending HTML. This can delay LCP.
Problem: Slow template generation delaying entire page load.
Diagnosis:
- Add timing code to template:
[[!+modx.startTime:default=`[[!+modx.config.request_time:set=`modx.startTime`]]`]]
<!-- Your template content -->
<!-- Page generated in [[!+modx.totalTime:default=`[[!+modx.config.request_time:subtract=`[[!+modx.startTime]]`:set=`modx.totalTime`]]`]] seconds -->
- Check MODX Manager: Reports → System Info → Enable debugging
- Use MODX Console plugin to profile snippet execution
Solutions:
A. Enable and Optimize MODX Caching
Most critical optimization for MODX:
Enable Resource Caching:
- Edit resource
- Page Settings tab
- Check Cacheable
- Check Empty Cache
- Set Cache Refresh to appropriate value (e.g., 3600 for 1 hour)
Global Cache Settings:
System Settings:
cache_expires → 3600 (or higher)
cache_handler → xPDOFileCache (default, fastest)
Verify cache is working:
// Add to template temporarily
[[!+modx.resource.cacheable:default=`Not cached!`]]
B. Cache Snippets
Convert uncached snippets to cached when possible:
<!-- Before: Uncached (slower) -->
[[!getResources? &parents=`5` &limit=`10`]]
<!-- After: Cached (faster) -->
[[getResources? &parents=`5` &limit=`10`]]
When to use uncached snippets:
- User-specific content (cart, user profile)
- Real-time data (stock levels, current time)
- Form tokens
- Session-dependent content
When to cache:
- Product listings
- Article/blog listings
- Navigation menus
- Static content blocks
C. Optimize Heavy Snippets
getResources optimization:
<!-- Bad: No limit, processes all resources -->
[[!getResources? &parents=`5`]]
<!-- Good: Limited results, specific fields -->
[[getResources?
&parents=`5`
&limit=`10`
&includeContent=`0`
&includeTVs=`0`
&processTVs=`0`
]]
pdoTools instead of getResources:
<!-- pdoResources is faster than getResources -->
[[pdoResources?
&parents=`5`
&limit=`10`
&includeTVs=`thumbnail`
&prepareTVs=`1`
]]
2. Unoptimized Images in Templates and TVs
Images from chunks, TVs, or uploaded via MODX File Browser often lack optimization.
Problem: Large hero images or product images loading slowly.
Diagnosis:
- Run PageSpeed Insights
- Check if images flagged as LCP element
- Look for "Properly size images" warning
- Inspect image file sizes in MODX File Browser
Solutions:
A. Use phpThumb for Image Resizing
MODX includes phpThumb for dynamic resizing:
<!-- Before: Full-size image -->
<img src="[[*hero_image]]" alt="[[*pagetitle]]">
<!-- After: Resized with phpThumb -->
<img
src="[[*hero_image:phpthumb=`w=1920&h=600&zc=1`]]"
srcset="[[*hero_image:phpthumb=`w=375&h=200&zc=1`]] 375w,
[[*hero_image:phpthumb=`w=750&h=400&zc=1`]] 750w,
[[*hero_image:phpthumb=`w=1920&h=600&zc=1`]] 1920w"
sizes="100vw"
width="1920"
height="600"
alt="[[*pagetitle]]"
loading="eager"
>
phpThumb parameters:
w= widthh= heightzc=1= zoom/cropq=80= quality (80 is good balance)
For pThumb extra (better performance):
[[*hero_image:pthumb=`w=1920&h=600&zc=1&q=80`]]
B. Set Explicit Dimensions
Always include width/height to prevent layout shift:
<!-- Get image dimensions from TV -->
<img
src="[[*hero_image:phpthumb=`w=1920`]]"
width="1920"
height="600"
alt="[[*pagetitle]]"
loading="eager"
>
C. Preload LCP Image
Add to template <head>:
<!-- Preload hero image on homepage -->
[[*id:is=`1`:then=`
<link
rel="preload"
as="image"
href="[[*hero_image:phpthumb=`w=1920&h=600&zc=1`]]"
imagesrcset="[[*hero_image:phpthumb=`w=375&h=200&zc=1`]] 375w,
[[*hero_image:phpthumb=`w=1920&h=600&zc=1`]] 1920w"
imagesizes="100vw"
>
`]]
D. Use WebP Format
Install ImageSlim or similar extra to convert to WebP:
[[*hero_image:phpthumb=`w=1920&h=600&f=webp&q=80`]]
Or use picture element for fallback:
<picture>
<source srcset="[[*hero_image:phpthumb=`w=1920&f=webp&q=80`]]" type="image/webp">
<img src="[[*hero_image:phpthumb=`w=1920&q=80`]]" alt="[[*pagetitle]]" width="1920" height="600">
</picture>
3. Plugin Overhead
Plugins executing on every page can significantly delay rendering.
Problem: Plugins on OnWebPagePrerender adding processing time.
Diagnosis:
Check plugin execution time:
<?php
// Add to plugin start
$startTime = microtime(true);
// Plugin code here...
// At plugin end
$execTime = microtime(true) - $startTime;
$modx->log(modX::LOG_LEVEL_INFO, "Plugin {$modx->event->name} took {$execTime}s");
Audit active plugins:
- Elements → Plugins
- Note which plugins are enabled
- Check System Events tab for each
- Disable non-essential plugins for testing
Solutions:
A. Disable Unnecessary Plugins
Disable plugins that run on every page but aren't needed:
- Development/debug plugins in production
- Unused tracking plugins
- Legacy plugins
B. Optimize Plugin Code
<?php
/**
* Optimized Plugin Example
* Only execute when necessary
*/
// Exit early if not needed
if ($modx->context->key === 'mgr') return; // Skip in manager
// Cache expensive operations
$cacheKey = 'plugin_data_' . $modx->resource->id;
$cacheOptions = [xPDO::OPT_CACHE_KEY => 'custom'];
$data = $modx->cacheManager->get($cacheKey, $cacheOptions);
if (!$data) {
// Expensive operation only if not cached
$data = performExpensiveOperation();
$modx->cacheManager->set($cacheKey, $data, 3600, $cacheOptions);
}
// Use cached data...
C. Move Logic to Snippets
Instead of plugin on every page, use cached snippet on specific templates:
<!-- Instead of plugin on all pages -->
[[snippetName]] <!-- Only on pages that need it -->
4. Template JavaScript and CSS
Render-blocking scripts in template head delay LCP.
Problem: JavaScript and CSS files blocking page render.
Diagnosis:
- Run PageSpeed Insights
- Look for "Eliminate render-blocking resources"
- Check Network tab for script load times
Solutions:
A. Use Defer for JavaScript
In template:
<!-- Before: Blocking -->
<script src="assets/js/theme.js"></script>
<!-- After: Deferred -->
<script src="assets/js/theme.js" defer></script>
<!-- Or async for non-dependent scripts -->
<script src="assets/js/analytics.js" async></script>
In chunk or template:
<script defer src="[[++assets_url]]js/theme.js"></script>
B. Inline Critical CSS
Create chunk with critical CSS:
Chunk: critical_css
/* Critical above-fold styles */
body { margin: 0; font-family: Arial, sans-serif; }
.header { background: #333; color: #fff; padding: 1rem; }
.hero { height: 600px; background-size: cover; }
In template head:
<style>
[[$critical_css]]
</style>
<!-- Load full CSS asynchronously -->
<link
rel="preload"
href="[[++assets_url]]css/theme.css"
as="style"
>
<noscript>
<link rel="stylesheet" href="[[++assets_url]]css/theme.css">
</noscript>
C. Minimize Template Output
<!-- Remove whitespace in production -->
[[++minify_templates:is=`1`:then=`<!-- minified -->`]]
<!-- Use ClientConfig for settings -->
[[!++site_name]] // Better than hardcoded
5. Database Query Optimization
Multiple TV queries and resource calls slow page generation.
Problem: Too many database queries for TVs and related resources.
Diagnosis:
Enable query logging temporarily:
System Settings:
debug → Yes
log_level → MODX_LOG_LEVEL_DEBUG
Check Reports → Error Log for query counts.
Solutions:
A. Reduce TV Usage
<!-- Bad: Each TV = separate query if not processed -->
[[*tv1]]
[[*tv2]]
[[*tv3]]
<!-- Better: Process TVs in snippet -->
[[getTVs? &tvs=`tv1,tv2,tv3`]]
Or use pdoTools to fetch TVs efficiently:
[[pdoResources?
&parents=`5`
&includeTVs=`tv1,tv2,tv3`
&prepareTVs=`1`
]]
B. Use Collections or Single Parent
Instead of multiple parent queries:
<!-- Bad: Multiple queries -->
[[getResources? &parents=`5,6,7,8,9`]]
<!-- Better: Single parent or depth -->
[[getResources? &parents=`5` &depth=`1`]]
C. Limit getResources Calls
<!-- Always set limits -->
[[getResources?
&parents=`5`
&limit=`10` // Don't fetch all
&offset=`0`
&includeContent=`0` // Skip if not needed
&includeTVs=`0` // Skip if not needed
]]
6. External Resources and Third-Party Scripts
GTM, analytics, and tracking scripts can delay LCP.
Problem: Analytics and marketing scripts blocking render.
Solutions:
A. Load Analytics Asynchronously
<!-- GTM with async -->
<script async>(function(w,d,s,l,i){...GTM code...})</script>
<!-- GA4 with async -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
B. Defer Non-Critical Tracking
<!-- Load tracking after LCP -->
<script>
window.addEventListener('load', function() {
// Load non-critical tracking
setTimeout(function() {
// Initialize chat widgets, social plugins, etc.
}, 2000);
});
</script>
C. Use GTM to Control Script Loading
Configure GTM to load tags after LCP:
- Trigger: DOM Ready or Window Loaded
- Instead of: All Pages (immediate)
7. Font Loading
Custom fonts in templates can delay LCP.
Problem: Web fonts blocking render.
Solutions:
A. Preload Fonts
<!-- In template head -->
<link
rel="preload"
href="[[++assets_url]]fonts/custom-font.woff2"
as="font"
type="font/woff2"
crossorigin
>
B. Use font-display: swap
@font-face {
font-family: 'CustomFont';
src: url('[[++assets_url]]fonts/custom-font.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
font-weight: 400;
font-style: normal;
}
C. Limit Font Variants
Only load fonts you actually use:
/* Bad: Loading too many variants */
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&display=swap');
/* Better: Only what's needed */
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap');
MODX Hosting and Server Optimization
Enable OPcache (PHP)
Dramatically improves PHP performance:
; php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
Verify in System → System Info → PHP Info: Look for OPcache enabled.
Enable MODX Cache
System Settings:
cache_disabled → No (ensure cache is enabled)
cache_expires → 3600 or higher
cache_handler → xPDOFileCache (fastest)
Use PHP 8.0+
MODX Revolution 3.x supports PHP 8.0+, which is significantly faster:
- Check current version: System → System Info
- Upgrade PHP via hosting control panel or contact host
- Test thoroughly after upgrade
Enable GZIP Compression
In .htaccess:
# Enable GZIP compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
</IfModule>
Database Optimization
Optimize MODX database tables:
-- Via phpMyAdmin or command line
OPTIMIZE TABLE modx_site_content;
OPTIMIZE TABLE modx_site_tmplvars;
OPTIMIZE TABLE modx_site_tmplvar_contentvalues;
Or use MODX plugin:
<?php
// Run weekly via cron
$tables = [
$modx->getTableName('modResource'),
$modx->getTableName('modTemplateVar'),
$modx->getTableName('modTemplateVarResource')
];
foreach ($tables as $table) {
$modx->exec("OPTIMIZE TABLE {$table}");
}
Testing & Monitoring
Test LCP
Tools:
- PageSpeed Insights - Lab and field data
- WebPageTest - Detailed waterfall
- Chrome DevTools → Lighthouse - Local testing
- Chrome DevTools → Performance - Identify LCP element
Test Multiple Pages:
- Homepage (often slowest)
- Content pages
- Product pages (if e-commerce)
- Different templates
Test Conditions:
- Desktop and mobile
- Fast 3G and 4G (throttling in DevTools)
- Different geographic locations
Monitor Over Time
- Core Web Vitals report
- Shows pages failing LCP
- Impact on SEO
Chrome User Experience Report (CrUX):
- Real user data in PageSpeed Insights
- 28-day rolling average
Custom Monitoring:
// Log page generation time
[[!+modx.totalTime]]
Track in GA4 or analytics to monitor trends.
Quick Wins Checklist
Start here for immediate LCP improvements:
- Enable MODX cache for all published resources
- Cache all snippets that don't need real-time data
- Resize and optimize hero images with phpThumb
- Add explicit width/height to images
- Preload LCP image in template head
- Use
deferon non-critical JavaScript - Disable unused plugins
- Limit getResources queries (max 10-20 results)
- Skip
includeContentandincludeTVswhen not needed - Enable OPcache on server
- Test with PageSpeed Insights
Common LCP Elements in MODX
| Page Type | Common LCP Element | Optimization Priority |
|---|---|---|
| Homepage | Hero banner from TV | Highest - Use phpThumb, preload |
| Articles | Featured image chunk | High - Optimize and set dimensions |
| Products | Product image TV | Highest - Resize, lazy load others |
| Listings | First getResources item | Medium - Cache snippet, limit |
When to Hire a Developer
Consider hiring a MODX developer if:
- LCP consistently over 4 seconds after optimizations
- Complex custom snippets need optimization
- Database queries require advanced optimization
- Need custom caching strategy
- Server configuration changes needed
Find MODX developers: MODX Professional Directory
Next Steps
For general LCP optimization strategies, see LCP Optimization Guide.