Largest Contentful Paint (LCP) measures how long it takes for the main content to load. Static sites built with Netlify CMS should inherently be fast, but tracking scripts, images, and third-party resources can slow down LCP.
What is LCP?
LCP measures when the largest visible content element renders.
Thresholds:
- Good: < 2.5 seconds
- Needs Improvement: 2.5 - 4.0 seconds
- Poor: > 4.0 seconds
Common LCP Elements on Static Sites:
- Hero images (homepage banners, featured images)
- Large text blocks (blog post titles, headings)
- Video thumbnails
- Code blocks in documentation sites
Measuring LCP on Static Sites
PageSpeed Insights
- Go to PageSpeed Insights
- Enter your site URL (production or preview deploy)
- View Largest Contentful Paint metric
- Check Diagnostics for specific issues
Lighthouse CLI
# Install Lighthouse
npm install -g lighthouse
# Run audit
lighthouse https://yoursite.com --view
# Mobile-specific
lighthouse https://yoursite.com --preset=mobile --view
WebPageTest
- Go to WebPageTest
- Enter URL
- Select location nearest your audience
- Choose device type (Mobile/Desktop)
- Run test
- View LCP in filmstrip and metrics
Common Causes of Slow LCP on Static Sites
1. Unoptimized Images
Symptoms:
- Large hero images (> 500 KB)
- Images not using modern formats (WebP, AVIF)
- Images larger than display size
- No lazy loading exclusions for LCP image
Solutions:
Use Static Site Generator Image Optimization
Hugo:
<!-- layouts/_default/baseof.html -->
{{ $image := resources.Get "images/hero.jpg" }}
{{ $webp := $image.Resize "1920x webp q85" }}
{{ $fallback := $image.Resize "1920x jpg q85" }}
<picture>
<source srcset="{{ $webp.RelPermalink }}" type="image/webp">
<img src="{{ $fallback.RelPermalink }}" alt="Hero" loading="eager" fetchpriority="high">
</picture>
// Install plugin
npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp
// gatsby-config.js
{
resolve: `gatsby-plugin-image`,
options: {
defaults: {
formats: [`auto`, `webp`, `avif`],
quality: 85,
}
}
}
// Component usage
import { StaticImage } from 'gatsby-plugin-image'
<StaticImage
src="../images/hero.jpg"
alt="Hero"
loading="eager"
placeholder="blurred"
width={1920}
height={1080}
/>
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
},
};
// Component
import Image from 'next/image';
<Image
src="/images/hero.jpg"
alt="Hero"
width={1920}
height={1080}
priority // Don't lazy load LCP image!
quality={85}
/>
Use Image CDN
Cloudinary:
<!-- Auto-format, auto-quality -->
<img src="https://res.cloudinary.com/demo/image/upload/f_auto,q_auto/hero.jpg"
loading="eager"
fetchpriority="high"
alt="Hero">
imgix:
<img src="https://yoursite.imgix.net/hero.jpg?auto=format,compress&w=1920"
loading="eager"
fetchpriority="high"
alt="Hero">
Exclude LCP Image from Lazy Loading
Hugo:
{{ if .IsHome }}
<!-- Homepage hero - no lazy loading -->
<img src="{{ $hero.RelPermalink }}" loading="eager" fetchpriority="high" alt="Hero">
{{ else }}
<!-- Other images - lazy load -->
<img src="{{ .Src }}" loading="lazy" alt="{{ .Alt }}">
{{ end }}
Jekyll:
{% if page.url == "/" %}
<img src="{{ site.hero_image }}" loading="eager" fetchpriority="high" alt="Hero">
{% else %}
<img src="{{ include.src }}" loading="lazy" alt="{{ include.alt }}">
{% endif %}
2. Render-Blocking Resources
Symptoms:
- Large CSS files (> 100 KB)
- Synchronous JavaScript in head
- Web fonts loading synchronously
- Multiple third-party scripts
Solutions:
Critical CSS Inline
Hugo:
<!-- layouts/partials/critical-css.html -->
<style>
{{ $critical := resources.Get "css/critical.css" }}
{{ $critical.Content | safeCSS }}
</style>
<!-- Load full CSS asynchronously -->
{{ $css := resources.Get "css/main.css" | minify | fingerprint }}
<link rel="preload" href="{{ $css.RelPermalink }}" as="style"
<noscript><link rel="stylesheet" href="{{ $css.RelPermalink }}"></noscript>
Gatsby:
# Install plugin
npm install gatsby-plugin-inline-critical-css
# gatsby-config.js
{
resolve: `gatsby-plugin-inline-critical-css`,
options: {
ignore: {
atrule: ['@font-face'],
},
},
}
Defer Non-Critical JavaScript
Universal approach:
<!-- Defer analytics scripts -->
<script defer src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script defer>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
Optimize Web Fonts
Preload critical fonts:
<link rel="preload" href="/fonts/primary-font.woff2" as="font" type="font/woff2" crossorigin>
Use font-display: swap:
@font-face {
font-family: 'YourFont';
src: url('/fonts/your-font.woff2') format('woff2');
font-display: swap; /* Show fallback text immediately */
}
System fonts (fastest):
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
3. Slow Server Response (TTFB)
Symptoms:
- High Time to First Byte (> 600ms)
- Slow initial HTML load
- Netlify edge response slow
Solutions:
Use Netlify CDN Properly
Ensure proper headers:
# netlify.toml
[[headers]]
for = "/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.html"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
Optimize Build Output
Hugo:
# config.toml
[minify]
minifyOutput = true
[build]
writeStats = true
Gatsby:
// gatsby-config.js
module.exports = {
flags: {
FAST_DEV: true,
PARALLEL_SOURCING: true,
},
};
Next.js:
// next.config.js
module.exports = {
compress: true,
swcMinify: true,
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
};
4. Third-Party Scripts (Analytics, Pixels)
Symptoms:
- GTM container blocks rendering
- GA4/Meta Pixel loads synchronously
- Chat widgets load on initial page load
Solutions:
Delay Third-Party Scripts
// Load analytics after user interaction
let analyticsLoaded = false;
function loadAnalytics() {
if (analyticsLoaded) return;
analyticsLoaded = true;
// Load GTM
(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');
}
// Trigger on first interaction
['mousedown', 'touchstart', 'keydown', 'scroll'].forEach(event => {
window.addEventListener(event, loadAnalytics, {once: true, passive: true});
});
// Fallback after 3 seconds
setTimeout(loadAnalytics, 3000);
Use Resource Hints
<link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="//connect.facebook.net">
Conditional Loading
Hugo:
{{ if eq (getenv "CONTEXT") "production" }}
{{ partial "analytics.html" . }}
{{ end }}
Gatsby:
// gatsby-config.js
{
resolve: `gatsby-plugin-google-gtag`,
options: {
includeInDevelopment: false, // Only load in production
}
}
5. Unoptimized Static Site Generator Build
Symptoms:
- Slow build times (> 5 minutes)
- Large bundle sizes
- Unused CSS/JS in output
Solutions:
Hugo Optimization
# config.toml
[build]
useResourceCacheWhen = "always"
[minify]
disableHTML = false
disableCSS = false
disableJS = false
disableJSON = false
disableSVG = false
disableXML = false
minifyOutput = true
Gatsby Optimization
# Use incremental builds
GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES=true gatsby build --log-pages
# Parallel image processing
npm install gatsby-plugin-sharp@latest
Next.js Optimization
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
swcMinify: true,
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
experimental: {
optimizeCss: true,
},
});
Static Site-Specific Optimizations
Preload LCP Image
<!-- Hugo -->
{{ if .IsHome }}
{{ $hero := resources.Get "images/hero.jpg" }}
<link rel="preload" as="image" href="{{ $hero.RelPermalink }}">
{{ end }}
<!-- Next.js - use priority prop on Image component -->
<Image src="/hero.jpg" priority />
<!-- Gatsby - use loading="eager" -->
<StaticImage src="../hero.jpg" loading="eager" />
Eliminate Render-Blocking Resources
Inline critical CSS, defer rest:
<style>
/* Critical above-the-fold CSS */
.hero { ... }
.header { ... }
</style>
<link rel="preload" href="/css/full.css" as="style"
<noscript><link rel="stylesheet" href="/css/full.css"></noscript>
Optimize Content Images
Responsive images:
<!-- Hugo -->
{{ $image := resources.Get .Src }}
{{ $small := $image.Resize "640x webp q85" }}
{{ $medium := $image.Resize "1024x webp q85" }}
{{ $large := $image.Resize "1920x webp q85" }}
<img
srcset="{{ $small.RelPermalink }} 640w,
{{ $medium.RelPermalink }} 1024w,
{{ $large.RelPermalink }} 1920w"
sizes="(max-width: 640px) 640px, (max-width: 1024px) 1024px, 1920px"
src="{{ $large.RelPermalink }}"
alt="{{ .Alt }}"
loading="lazy">
Testing LCP Improvements
Before/After Comparison
- Baseline: Run Lighthouse before changes
- Make optimizations
- Re-test: Clear cache, run Lighthouse again
- Compare: Note LCP improvement
Test on Preview Deploy
# Make changes in branch
git checkout -b optimize-lcp
# Commit changes
git add .
git commit -m "Optimize LCP"
git push origin optimize-lcp
# Create pull request
# Netlify generates preview deploy
# Test preview deploy URL with Lighthouse
Monitor Real Users
Use GA4 to track real-user LCP:
// Report Core Web Vitals to GA4
import {getCLS, getFID, getFCP, getLCP, getTTFB} from 'web-vitals';
function sendToAnalytics({name, delta, id}) {
gtag('event', name, {
event_category: 'Web Vitals',
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
event_label: id,
non_interaction: true,
});
}
getLCP(sendToAnalytics);
Checklist for LCP Optimization
- Optimize LCP image - WebP/AVIF format, proper sizing
- Don't lazy load LCP image - Use
loading="eager" fetchpriority="high" - Preload LCP image - Resource hint for critical image
- Inline critical CSS - Above-the-fold styles in
<head> - Defer non-critical JS - Analytics, chat widgets
- Optimize web fonts - Preload, font-display: swap
- Use CDN - Netlify Edge for fast global delivery
- Minimize third-party scripts - Load after interaction
- Enable compression - Gzip/Brotli on Netlify
- Optimize build output - Minify HTML/CSS/JS
Next Steps
- Fix CLS Issues - Cumulative Layout Shift
- Debug Tracking Impact - Ensure tracking doesn't slow LCP
- Global LCP Guide - Universal concepts
Related Resources
- Hugo Performance
- Gatsby Performance
- Next.js Performance
- Core Web Vitals - Universal performance concepts