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. ProcessWire is a PHP CMS with a powerful selector-based API, built-in image manipulation, and flexible template rendering. LCP depends on database query efficiency, image variation generation, and whether full-page caching is enabled.
ProcessWire-Specific LCP Causes
- No built-in page cache -- ProcessWire executes PHP templates and database queries on every request unless ProCache or a custom cache is configured
- Unresized images -- using
$image->urldirectly serves full-resolution originals instead of generated variations - Complex selector queries --
$pages->find()with many conditions on large sites can be slow without proper indexes - Template file includes -- deeply nested
$files->include()calls in template files add PHP overhead - Module overhead -- many active modules (FormBuilder, ProFields, etc.) add hooks that execute on every page load
Fixes
1. Enable ProCache or Implement Page Caching
ProCache (commercial module) provides static HTML caching:
// In /site/config.php
$config->proCacheEnabled = true;
$config->proCacheTime = 3600; // 1 hour
// Manual cache alternative (free):
// In your template file (e.g., home.php)
$cacheKey = 'page_' . $page->id . '_' . $page->modified;
$cached = $cache->get($cacheKey);
if ($cached) {
echo $cached;
return;
}
ob_start();
// ... your template rendering code ...
$output = ob_get_contents();
ob_end_flush();
$cache->save($cacheKey, $output, 3600);
2. Use ProcessWire's Image Resizing API
Always generate appropriately sized variations instead of serving originals:
// In your template file
$hero = $page->hero_image;
if ($hero) {
// Create a resized variation (cached automatically)
$img = $hero->size(1200, 630, ['quality' => 80, 'upscaling' => false]);
echo "<img
src='{$img->url}'
width='{$img->width}' height='{$img->height}'
alt='{$page->title}'
loading='eager'
fetchpriority='high'
style='aspect-ratio: {$img->width}/{$img->height}; width: 100%; height: auto; object-fit: cover;'
>";
}
Generate WebP variations:
// In /site/config.php
$config->imageSizerOptions('webpAdd', true);
$config->imageSizerOptions('webpQuality', 80);
// In template -- ProcessWire auto-generates .webp alongside .jpg
$img = $hero->size(1200, 630);
echo "<picture>
<source srcset='{$img->webp->url}' type='image/webp'>
<img src='{$img->url}' width='{$img->width}' height='{$img->height}'
alt='{$page->title}' loading='eager' fetchpriority='high'>
</picture>";
3. Optimize Selector Queries
// SLOW: Fetching all fields for a listing
$posts = $pages->find("template=blog-post, sort=-date, limit=10");
// FASTER: Only load fields you need
$posts = $pages->find("template=blog-post, sort=-date, limit=10");
// Use autojoin for frequently accessed fields
// In /site/config.php:
$config->dbCache = true;
// Or use $pages->findRaw() for read-only listing data
$posts = $pages->findRaw("template=blog-post, sort=-date, limit=10", [
'title', 'date', 'summary', 'hero_image'
]);
4. Inline Critical CSS and Defer Theme Styles
<!-- In your _main.php or head include -->
<head>
<style>
/* Critical above-fold CSS */
body { font-family: system-ui, sans-serif; margin: 0; }
.header { display: flex; height: 64px; align-items: center; }
.hero { width: 100%; aspect-ratio: 16/9; }
.content { max-width: 800px; margin: 0 auto; padding: 1rem; }
</style>
<link rel="preload" href="<?= $config->urls->templates ?>styles/main.css"
as="style"
<noscript>
<link rel="stylesheet" href="<?= $config->urls->templates ?>styles/main.css">
</noscript>
</head>
5. Add Server-Level Caching
# .htaccess in ProcessWire root
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>
Measuring LCP on ProcessWire
- ProcessWire Debug Mode -- enable
$config->debug = truein config.php to see query count and render time in the admin footer - Tracy Debugger module -- install for detailed profiling of selectors, hooks, and template render time
- PageSpeed Insights -- test homepage and content-heavy listing pages
- TTFB check --
curl -w "TTFB: %{time_starttransfer}\n" -o /dev/null -s https://yoursite.com-- if over 400ms without cache, implement ProCache
Analytics Script Impact
- ProcessWire has no built-in analytics -- all tracking is manually added to templates
- Place
<script async>tags at the bottom of_main.phpbefore</body> - Use ProcessWire's markup regions or
$config->scripts->append()to manage script loading order - Avoid synchronous scripts in template header includes