Overview
Largest Contentful Paint (LCP) measures how quickly the main content of a page loads. Google considers LCP a critical metric for page experience and SEO ranking.
LCP Targets:
- Good: < 2.5 seconds
- Needs Improvement: 2.5 - 4.0 seconds
- Poor: > 4.0 seconds
Check Your LCP
Method 1: PageSpeed Insights
1. Go to https://pagespeed.web.dev/
2. Enter your OSCommerce store URL
3. Check LCP score under "Diagnostics"
Method 2: Chrome DevTools
1. Open page in Chrome
2. Press F12 > Lighthouse tab
3. Click "Generate report"
4. Check LCP under "Performance"
Method 3: Web Vitals Extension
1. Install Web Vitals Chrome extension
2. Visit your store
3. Click extension to see real-time LCP
Common LCP Elements in OSCommerce
Typical LCP elements on OSCommerce pages:
- Homepage: Hero banner image, featured product images
- Product Pages: Main product image
- Category Pages: First product images or banner
- Text Content: Product descriptions, category headings
Diagnosis: Identify Your LCP Element
Using Chrome DevTools:
1. F12 > Performance tab
2. Click Record (circle icon)
3. Reload page
4. Stop recording
5. Look for "LCP" marker in timeline
6. Hover to see which element is LCP
Fix 1: Optimize Images
Images are the most common LCP element. Optimize them for faster loading.
Compress Images
Before uploading to OSCommerce:
# Use image compression tools:
# - TinyPNG (https://tinypng.com/)
# - ImageOptim (Mac)
# - Squoosh (https://squoosh.app/)
# Target sizes:
# Product images: < 100KB
# Banner images: < 200KB
# Thumbnails: < 30KB
Convert to Modern Formats
Use WebP for better compression:
<?php
// File: catalog/includes/functions/html_output.php
// Add WebP support function
function tep_image_webp($src, $alt = '', $width = '', $height = '', $parameters = '') {
$webp_src = preg_replace('/\.(jpg|jpeg|png)$/i', '.webp', $src);
if (file_exists($webp_src)) {
return '<picture>
<source srcset="' . $webp_src . '" type="image/webp">
<img src="' . $src . '" alt="' . $alt . '" width="' . $width . '" height="' . $height . '" ' . $parameters . '>
</picture>';
} else {
return tep_image($src, $alt, $width, $height, $parameters);
}
}
?>
Serve Appropriately Sized Images
File: catalog/product_info.php
<?php
// Don't load huge images that will be displayed small
// BAD - Loading 2000x2000 image for 400px display
echo tep_image(DIR_WS_IMAGES . $product_info['products_image'], $product_info['products_name']);
// GOOD - Generate/use appropriately sized image
$image_size = 600; // Display size
echo tep_image(DIR_WS_IMAGES . $product_info['products_image'], $product_info['products_name'], $image_size, $image_size);
?>
Add Width and Height Attributes
Prevent layout shifts and speed up rendering:
<?php
// File: catalog/includes/functions/html_output.php
function tep_image($src, $alt = '', $width = '', $height = '', $parameters = '') {
// Always include width and height
if (!$width || !$height) {
$image_path = DIR_FS_CATALOG . $src;
if (file_exists($image_path)) {
list($width, $height) = getimagesize($image_path);
}
}
$image = '<img src="' . tep_output_string($src) . '" alt="' . tep_output_string($alt) . '"';
if ($width) {
$image .= ' width="' . tep_output_string($width) . '"';
}
if ($height) {
$image .= ' height="' . tep_output_string($height) . '"';
}
if ($parameters) {
$image .= ' ' . $parameters;
}
$image .= '>';
return $image;
}
?>
Fix 2: Implement Lazy Loading
Don't load images that aren't visible initially.
Native Lazy Loading
File: catalog/includes/functions/html_output.php
<?php
function tep_image($src, $alt = '', $width = '', $height = '', $parameters = '', $lazy = true) {
$image = '<img src="' . tep_output_string($src) . '" alt="' . tep_output_string($alt) . '"';
if ($width) $image .= ' width="' . $width . '"';
if ($height) $image .= ' height="' . $height . '"';
// Add lazy loading for non-LCP images
if ($lazy) {
$image .= ' loading="lazy"';
}
if ($parameters) $image .= ' ' . $parameters;
$image .= '>';
return $image;
}
?>
Usage:
<?php
// Product page - main image should NOT be lazy loaded (it's LCP)
echo tep_image($main_image, $alt, 600, 600, '', false); // lazy = false
// Thumbnail images - should be lazy loaded
foreach ($thumbnails as $thumb) {
echo tep_image($thumb, $alt, 100, 100, '', true); // lazy = true
}
?>
Lazy Load Below the Fold
<!-- Images in viewport: NO lazy loading -->
<img src="hero-banner.jpg" alt="Banner" width="1200" height="400">
<!-- Images below fold: YES lazy loading -->
<img src="product-1.jpg" alt="Product" width="300" height="300" loading="lazy">
<img src="product-2.jpg" alt="Product" width="300" height="300" loading="lazy">
Fix 3: Preload LCP Image
Tell the browser to load the LCP image as early as possible.
File: catalog/includes/header.php
<?php
// Identify LCP image per page type
$lcp_image = null;
// Homepage - hero banner
if (basename($_SERVER['PHP_SELF']) == 'index.php' && !isset($_GET['cPath'])) {
$lcp_image = 'images/hero-banner.jpg';
}
// Product page - main product image
if (basename($_SERVER['PHP_SELF']) == 'product_info.php' && isset($product_info)) {
$lcp_image = DIR_WS_IMAGES . $product_info['products_image'];
}
// Category page - first product or category banner
if (isset($_GET['cPath']) && isset($listing[0])) {
$lcp_image = DIR_WS_IMAGES . $listing[0]['products_image'];
}
// Preload LCP image
if ($lcp_image) {
?>
<link rel="preload" as="image" href="<?php echo $lcp_image; ?>">
<?php
}
?>
Preload with fetchpriority
For modern browsers:
<link rel="preload" as="image" href="hero-banner.jpg" fetchpriority="high">
<!-- Or directly on img tag -->
<img src="hero-banner.jpg" fetchpriority="high" width="1200" height="400">
Fix 4: Optimize Server Response Time
Slow server = slow LCP.
Enable PHP OPcache
File: php.ini or contact hosting provider
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
Enable Database Query Caching
File: catalog/includes/configure.php
<?php
// Enable MySQL query cache (if available)
define('USE_PCONNECT', 'true'); // Persistent connections
// Or implement object caching
define('USE_CACHE', 'true');
define('CACHE_LIFETIME', 3600); // 1 hour
?>
Implement Page Caching
Cache generated pages to reduce server load:
<?php
// File: catalog/includes/application_top.php
// Simple page cache
$cache_file = 'cache/page_' . md5($_SERVER['REQUEST_URI']) . '.html';
$cache_time = 3600; // 1 hour
if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $cache_time) {
// Serve cached version
readfile($cache_file);
exit;
}
// Generate page normally
ob_start();
// ... rest of OSCommerce code ...
// At end of page:
// File: catalog/includes/application_bottom.php
$content = ob_get_contents();
file_put_contents($cache_file, $content);
ob_end_flush();
?>
Fix 5: Use a CDN
Serve static assets from a CDN for faster delivery.
Implement CDN for Images
File: catalog/includes/configure.php
<?php
// Define CDN URL
define('CDN_URL', 'https://cdn.yourstore.com/');
// Update image paths
define('DIR_WS_IMAGES', CDN_URL . 'images/');
define('DIR_WS_IMAGES_CATALOG', CDN_URL . 'images/catalog/');
?>
Popular CDN Options
- Cloudflare - Free tier available
- BunnyCDN - Affordable, fast
- CloudFront - AWS CDN
- KeyCDN - Pay-as-you-go
Fix 6: Optimize CSS
Render-blocking CSS delays LCP.
Inline Critical CSS
File: catalog/includes/header.php
<head>
<!-- Inline critical CSS -->
<style>
/* Above-the-fold styles only */
body { margin: 0; font-family: Arial, sans-serif; }
.header { background: #fff; padding: 20px; }
.hero { width: 100%; height: 400px; }
.product-main { max-width: 600px; }
</style>
<!-- Defer non-critical CSS -->
<link rel="preload" href="stylesheet.css" as="style"
<noscript><link rel="stylesheet" href="stylesheet.css"></noscript>
</head>
Remove Unused CSS
Eliminate CSS not used on the page:
# Use PurgeCSS or similar tool
# https://purgecss.com/
# Analyze with Coverage tool:
# Chrome DevTools > More tools > Coverage
# See which CSS is unused
Minify CSS
<?php
// File: catalog/includes/header.php
// Use minified CSS in production
if (PRODUCTION_MODE) {
$stylesheet = 'stylesheet.min.css';
} else {
$stylesheet = 'stylesheet.css';
}
?>
<link rel="stylesheet" href="<?php echo $stylesheet; ?>">
Fix 7: Optimize Web Fonts
Web fonts can delay LCP if not loaded efficiently.
Use font-display: swap
File: CSS file
@font-face {
font-family: 'CustomFont';
src: url('fonts/customfont.woff2') format('woff2');
font-display: swap; /* Show fallback font immediately */
}
Preload Critical Fonts
File: catalog/includes/header.php
<link rel="preload" href="fonts/mainfont.woff2" as="font" type="font/woff2" crossorigin>
Self-Host Google Fonts
Instead of loading from Google:
<!-- DON'T use Google CDN -->
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<!-- DO self-host fonts -->
<link rel="preload" href="fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face {
font-family: 'Roboto';
src: url('fonts/roboto.woff2') format('woff2');
font-display: swap;
}
</style>
Fix 8: Reduce JavaScript Impact
JavaScript can block LCP rendering.
Defer Non-Critical JavaScript
File: catalog/includes/header.php
<!-- DON'T load JavaScript in head without defer -->
<script src="jquery.js"></script>
<!-- DO defer JavaScript -->
<script src="jquery.js" defer></script>
<!-- Or load at end of body -->
</body>
<script src="scripts.js"></script>
</html>
Load Analytics Asynchronously
<!-- GA4 already loads async -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<!-- GTM already loads async -->
<script>(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
Fix 9: Upgrade Hosting
Sometimes the problem is hosting performance.
Signs You Need Better Hosting
- Server response time > 600ms
- Frequent downtime
- Limited resources (CPU, RAM)
- Old PHP version (< 7.4)
- No SSD storage
- Shared hosting with resource limits
Recommended Hosting Options
- VPS - More resources, better performance
- Managed WordPress/PHP hosting - Optimized servers
- Cloud hosting - AWS, Google Cloud, DigitalOcean
- Dedicated server - Maximum performance
Testing Improvements
Before and After Comparison
1. Test before changes: https://pagespeed.web.dev/
2. Record LCP score
3. Implement fixes
4. Test after changes
5. Compare improvement
Real User Monitoring
Track actual user LCP:
<script>
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
// Send to analytics
gtag('event', 'web_vitals', {
'event_category': 'Web Vitals',
'event_label': 'LCP',
'value': Math.round(lastEntry.renderTime || lastEntry.loadTime),
'metric_value': Math.round(lastEntry.renderTime || lastEntry.loadTime)
});
}).observe({type: 'largest-contentful-paint', buffered: true});
</script>
Quick Wins Checklist
Fastest improvements with biggest impact:
- Compress and optimize all images
- Add width/height to all images
- Preload LCP image
- Enable gzip/brotli compression
- Use browser caching
- Defer non-critical JavaScript
- Minify CSS and JavaScript
- Enable OPcache
- Use CDN for images
- Remove unused CSS/JS
Advanced Optimizations
HTTP/2 Push
Push LCP resources:
# .htaccess
<IfModule mod_http2.c>
H2PushResource add css/stylesheet.css
H2PushResource add images/hero-banner.jpg
</IfModule>
Adaptive Image Loading
Load different image sizes based on device:
<picture>
<source media="(max-width: 640px)" srcset="hero-mobile.jpg">
<source media="(max-width: 1024px)" srcset="hero-tablet.jpg">
<img src="hero-desktop.jpg" alt="Hero" width="1200" height="400">
</picture>
Monitoring
Set up continuous monitoring:
1. Google Search Console - Core Web Vitals report
2. PageSpeed Insights - Regular tests
3. Real User Monitoring - Track actual users
4. Lighthouse CI - Automated testing