13 min read · Web Development · Last updated July 2026
Quick answer: To fix LCP, attack the four-phase waterfall in order: (1) reduce TTFB below 800ms with better hosting and caching, (2) eliminate render-blocking resources, (3) preload the LCP resource with fetchpriority=”high”, (4) compress and size the LCP image correctly. Never apply lazy loading to your LCP image.
Introduction
Largest Contentful Paint is the Core Web Vital that most directly correlates with user perception of page speed. It answers the question users unconsciously ask: “when can I see the main content?” A fast LCP — under 2.5 seconds — signals to the user that the page is loading. A slow LCP leaves them staring at a blank or partially-rendered screen, increasing the probability they leave.
Of the three Core Web Vitals, LCP is where the largest performance gains are typically found, because it’s directly tied to tangible optimizations: hosting quality, caching strategy, image compression, and resource loading order.
This guide gives you a complete, step-by-step LCP optimization workflow — from identifying your LCP element to deploying preloads, serving modern image formats, and understanding how font loading affects text-based LCP elements.
In this guide, you’ll learn:
– How to find your LCP element precisely using PageSpeed Insights and WebPageTest filmstrip
– The hierarchy of LCP improvements (what to fix first vs what to fix last)
– Exactly how to implement <link rel="preload"> and fetchpriority="high" for maximum impact
– How CDN delivery, image formats, srcset, and font loading affect LCP in practice
Table of Contents
- Finding Your LCP Element
- The LCP Optimization Hierarchy
- Fix 1: Reduce Time to First Byte (TTFB)
- Fix 2: Eliminate Render-Blocking Resources
- Fix 3: Optimize the LCP Resource Itself
- Fix 4: Preload the LCP Resource
- Fix 5: Never Lazy-Load the LCP Image
- Fix 6: CDN Delivery and Modern Image Formats
- Fix 7: Font Loading and Text-Based LCP
- LCP Diagnostic Decision Tree
- Image Optimization Calculator
- FAQ
- Conclusion
Finding Your LCP Element
Before optimizing LCP, you need to know with certainty what element the browser is treating as the LCP candidate. Don’t guess — the browser’s choice can surprise you.
Method 1: PageSpeed Insights
Run your URL through PageSpeed Insights. In the “Diagnostics” section of the lab results, look for the “Largest Contentful Paint element” diagnostic. It will show you the specific HTML element — something like <img src="/hero.webp"> or <h1>Your Headline</h1> — with a screenshot highlighting the element.
On mobile, the viewport is narrower, so the LCP element is often different from desktop. A desktop hero image at 1400×600px that covers most of the screen may rank lower in area than a tall promotional text block on mobile. Always check both device types.
Method 2: WebPageTest Filmstrip
Run your URL on webpagetest.org and look at the Filmstrip tab. This gives you frame-by-frame screenshots of the page loading, with the LCP timestamp marked. You can visually see exactly which element appears last — and at what time — making it crystal clear what the browser identifies as LCP.
Choose a test location close to your users. If your audience is in South Asia, test from Singapore or Mumbai rather than the default Virginia. TTFB measured from a distant server can be 2–3x lower than what real users experience.
Method 3: Chrome DevTools
Open DevTools → Performance → record a page load. After recording, look at the “Timings” row in the timeline — you’ll see an “LCP” marker. Click it to see the associated element in the DOM. This is particularly useful for LCP elements that change dynamically (e.g., a carousel where the first slide is the LCP candidate).
Method 4: JavaScript console
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
console.log("LCP element:", lcp.element);
console.log("LCP time:", lcp.startTime);
}).observe({ type: "largest-contentful-paint", buffered: true });
Run this in the browser console to log the LCP element and its timestamp as the page loads.
The LCP Optimization Hierarchy
Not all LCP fixes are created equal. This hierarchy reflects the typical order of impact — always start at the top:
- Reduce TTFB (highest leverage — delays everything else)
- Eliminate render-blocking resources (unblocks LCP resource discovery)
- Preload the LCP resource (ensures early download start)
- Remove lazy loading from LCP image (prevents intentional download delay)
- Optimize the LCP resource itself (reduce download time)
- Serve from CDN (reduce network latency)
- Optimize font loading (for text-based LCP)
In practice, most sites need fixes at multiple levels. But if your TTFB is 2 seconds, no amount of image compression will get you to a 2.5s LCP — fix the server first.
Fix 1: Reduce Time to First Byte (TTFB)
TTFB is the time from the browser sending an HTTP request to receiving the first byte of the response. Google’s target: under 800ms. In the LCP waterfall, TTFB is Phase 1 — a slow TTFB delays the start of every subsequent phase.
Diagnosing TTFB
In Chrome DevTools → Network tab → click your HTML document request → look at the “Waiting (TTFB)” row in the Timing panel. Alternatively, WebPageTest reports TTFB prominently. Test from multiple locations to separate server performance from network latency.
Typical TTFB culprits and fixes
Shared hosting with no caching: Shared hosting can produce TTFB of 1–3 seconds under moderate load. Pages generated by WordPress or PHP without any caching require a database query and template render for every visitor. Fix: add a page caching plugin (WP Rocket, W3 Total Cache with page cache enabled) that serves pre-built HTML files.
No server-side caching: Even on good hosting, database-driven pages without server-side caching are slow. Implement Redis or Memcached for object caching. For WordPress, Redis Object Cache plugin connecting to a Redis server can reduce TTFB from 800ms to under 100ms.
No CDN or wrong CDN configuration: If your origin server is in the US and your users are in Australia, the HTML response travels ~15,000km before the browser even starts. Use a CDN with edge nodes near your users — Cloudflare has over 300 edge locations. Even for dynamic content, Cloudflare’s tiered caching can dramatically reduce TTFB.
Slow database queries: If your origin server is fast but your app’s database queries are slow, TTFB suffers. Profile with query monitoring tools (New Relic, Datadog, or MySQL slow query log). Index frequently-queried columns; avoid N+1 query patterns.
TTFB targets by tier
| Hosting type | Typical TTFB | After optimization |
|---|---|---|
| Shared hosting, no cache | 1,500–3,000ms | 200–600ms with page cache |
| VPS, no cache | 300–800ms | 50–150ms with Redis + page cache |
| Managed WordPress (WP Engine, Kinsta) | 100–300ms | 30–100ms with CDN |
| Static site (Netlify, Vercel, Cloudflare Pages) | 20–80ms | 10–40ms from edge |
Fix 2: Eliminate Render-Blocking Resources
Between TTFB and when the browser begins downloading the LCP resource lies Phase 2: resource load delay. Every render-blocking resource that appears in <head> before the LCP resource extends this phase.
A resource is render-blocking if it prevents the browser from constructing the render tree before it finishes loading. By default: synchronous <script> tags and <link rel="stylesheet"> tags are both render-blocking.
Identifying render-blocking resources
PageSpeed Insights reports “Eliminate render-blocking resources” with a list of offending files and their estimated delay. Chrome DevTools → Performance → look for the “Render blocking” label on requests in the Network waterfall.
Fixes
Defer non-critical JavaScript:
<!-- Before: blocks parsing -->
<script src="/analytics.js"></script>
<!-- After: loads after HTML is parsed -->
<script src="/analytics.js" defer></script>
<!-- Or: loads asynchronously (no guaranteed order) -->
<script src="/widget.js" async></script>
Use defer for scripts that depend on the DOM. Use async for independent scripts (analytics, ad tags). Neither defer nor async is appropriate for scripts that must run before first render.
Inline critical CSS, defer the rest:
The ideal pattern is to inline the CSS required for above-the-fold content directly in <head>, then load the full stylesheet asynchronously:
Tools like Critical (Node.js), PurgeCSS for Webpack, or WP Rocket’s “Optimize CSS Delivery” setting automate this extraction.
Remove unused CSS: Tools like Chrome DevTools → Coverage tab show which CSS rules are unused on a given page. Unused CSS from page builder themes (Elementor, Divi) and plugin stylesheets can add 50–300KB of blocking CSS.
Fix 3: Optimize the LCP Resource Itself
Phase 3 of the LCP waterfall is resource load duration — how long the image or font takes to download. For image-based LCP (the most common case), this means compression, sizing, and format selection.
Image format selection
| Format | vs JPEG (same quality) | Browser support | Best for |
|---|---|---|---|
| WebP | 25–35% smaller | 96%+ (all modern browsers) | Photos, general images |
| AVIF | 50–60% smaller | 90%+ (Chrome, Firefox, Safari 16+) | Maximum compression |
| JPEG | Baseline | Universal | Legacy fallback |
Use <picture> with srcset to serve modern formats with fallbacks:
<picture>
<source type="image/avif" srcset="/hero.avif 1x, /hero@2x.avif 2x">
<source type="image/webp" srcset="/hero.webp 1x, /hero@2x.webp 2x">
<img src="/hero.jpg" alt="Hero image" width="1200" height="600"
fetchpriority="high">
</picture>
Serve correctly sized images
A common error: uploading a 2400×1200px image for a container that renders at 600×300px on mobile. The browser downloads all 2,400 pixels and scales it down — wasted bandwidth directly adding to LCP time.
Use srcset with sizes to serve appropriately sized images:
<img
src="/hero-800.webp"
srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 80vw, 1200px"
alt="Hero product image"
width="1200" height="600"
fetchpriority="high">
This tells the browser to download the 400w version on a 400px-wide mobile viewport, 800w on a tablet, etc. The savings are significant: a 2400px image at 250KB might serve a 400px version at 25KB — a 90% reduction in download time for mobile users.
Compression targets
- JPEG: aim for 70–80% quality setting, then apply progressive encoding
- WebP: lossless quality 80 or lossy quality 75 is typically indistinguishable from original
- AVIF: quality 60–70 with crf 30–40 in most encoders
- Maximum file size target for LCP image: 100KB for above-the-fold hero image
Fix 4: Preload the LCP Resource
Preloading is the highest-leverage single HTML change you can make for LCP. Without preloading, the browser discovers the LCP image only after it finishes parsing all preceding HTML and render-blocking resources. With preloading, the browser starts downloading the LCP image in parallel with render-blocking resources.
The preload tag
<head>
<!-- Add this as early as possible in <head>, before any stylesheets -->
<link
rel="preload"
href="/hero.webp"
as="image"
fetchpriority="high"
type="image/webp"
>
</head>
Key attributes:
– rel="preload" — tells the browser to fetch this resource early
– href — the exact URL of the image (must match what the <img> tag will request)
– as="image" — required for the browser to correctly prioritize and cache the resource
– fetchpriority="high" — elevates this resource to the highest fetch priority queue
– type="image/webp" — optional but prevents downloading if the browser doesn’t support WebP
Preloading with srcset
If your image uses srcset, the preload tag needs to match:
<link
rel="preload"
href="/hero-800.webp"
as="image"
imagesrcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
imagesizes="(max-width: 600px) 100vw, 800px"
fetchpriority="high"
>
The imagesrcset and imagesizes attributes on the preload link mirror the srcset and sizes on the <img> tag, letting the browser download the correct variant based on the current viewport.
The fetchpriority=”high” attribute
Even without a preload tag, adding fetchpriority="high" directly to the LCP <img> element improves its download priority over other images on the page:
<img src="/hero.webp" alt="Hero" width="1200" height="600" fetchpriority="high">
Use both — the preload tag for early discovery, and fetchpriority="high" on the image element itself.
Fix 5: Never Lazy-Load the LCP Image
This is the most common and most damaging single mistake in LCP optimization. loading="lazy" tells the browser to intentionally delay downloading an image until it’s near the viewport. But your LCP image IS in the viewport from the moment the page loads.
Applying lazy loading to the LCP image instructs the browser to wait until the page is nearly fully rendered before downloading the most important visual element. The result: LCP times of 4–8 seconds on pages with otherwise reasonable performance.
<!-- Never do this for your LCP image -->
<img src="/hero.webp" loading="lazy" alt="Hero">
<!-- Correct: no lazy loading on LCP image -->
<img src="/hero.webp" fetchpriority="high" alt="Hero" width="1200" height="600">
The pattern that creates this mistake: developers apply loading="lazy" globally to all images through a JavaScript library or CMS setting, then forget that the hero image is included. Always explicitly exclude the LCP image from lazy loading.
In WordPress: if using plugins like WP Rocket or a theme that adds loading="lazy" globally, check for an exclusion setting for the first image or LCP image. WP Rocket has an “Excluded” field in the LazyLoad settings for this purpose.
Fix 6: CDN Delivery and Modern Image Formats
Even with an optimized image and a fast server, a user in Sydney downloading your image from a server in London experiences ~200–280ms of pure network latency before a single byte arrives. A CDN solves this by serving your images from an edge node geographically close to the user.
CDN options for image delivery
Cloudflare (free tier): Cloudflare’s free plan caches static assets including images at its global edge. Setup requires only changing your DNS nameservers. Cloudflare also automatically serves WebP to browsers that support it through Polish (paid feature), but even without that, caching alone typically reduces image load times by 40–70%.
BunnyCDN: Extremely cost-effective ($0.01–0.06 per GB) and straightforward to configure for image CDN use. Supports WebP optimization and image resizing at the edge on their Optimizer plan.
Cloudinary / Imgix: Purpose-built image CDN services that handle format conversion, resizing, and compression on the fly. You upload once; they serve the optimal format and size based on the requesting browser and viewport. Pricing is per transformation and bandwidth. For sites with large image libraries, these services eliminate manual image optimization entirely.
Shopify/WordPress-hosted: Shopify automatically serves WebP and AVIF for all uploaded images through its CDN. WordPress.com and managed WordPress hosts (WP Engine, Kinsta) include CDN-served images. If you’re on self-hosted WordPress, use Cloudflare or ShortPixel CDN for similar functionality.
Fix 7: Font Loading and Text-Based LCP
When the LCP element is a text block (H1, large paragraph, hero text), LCP is determined by when that text finishes rendering with its final font. A custom web font that loads late keeps the text invisible (with font-display: block) or renders with a layout-shifting fallback (with font-display: swap) — both delay LCP.
Understanding font-display options for LCP text
@font-face {
font-family: "YourFont";
src: url("/font.woff2") format("woff2");
font-display: swap; /* Shows fallback immediately, swaps to custom — causes CLS */
/* font-display: block; Shows nothing until font loads — worst for LCP */
/* font-display: optional; Uses fallback if not cached — best for LCP + CLS */
/* font-display: fallback; 100ms block, then uses fallback — compromise */
}
For text-based LCP, the recommendation depends on your use case:
- Landing pages where the exact font matters: Preload the font and use
font-display: swap - Body text and content: Use
font-display: optional— it accepts system fonts if the custom font isn’t cached - Google Fonts: Self-host the font files instead of loading from
fonts.googleapis.com— this eliminates one additional DNS lookup and connection
Preloading fonts for text LCP
<link
rel="preload"
href="/fonts/heading-bold.woff2"
as="font"
type="font/woff2"
crossorigin
>
The crossorigin attribute is required even for same-origin fonts. Without it, the browser fetches the font twice — once for preload, once for actual use.
Font fallback metric matching
The biggest LCP gain for font-optimized pages comes from matching your fallback font’s metrics (line height, character width) to the web font. When the swap happens, if the fallback and web font are the same visual size, there’s no layout shift and no text reflow — reducing both CLS and the render delay that text reflow causes.
CSS size-adjust, ascent-override, descent-override, and line-gap-override let you modify fallback font metrics to match your web font. Tools like Font Style Matcher and the @font-face descriptor approach make this achievable without manually calculating values.
LCP Diagnostic Decision Tree
Use this interactive decision tree to identify your highest-priority LCP fix.
🌳 LCP Diagnostic Decision Tree
Answer each question to find your most impactful LCP fix. Restart anytime to explore another path.
Image Optimization Calculator
Estimate the file size savings from converting your LCP image to WebP or AVIF.
🖼 LCP Image Optimization Calculator
Enter your current image details to see estimated savings and download time improvements.
FAQ
Q: My LCP is different on mobile vs desktop in PageSpeed Insights. Which should I prioritize?
Both matter for their respective ranking signals, but mobile is more critical given Google’s mobile-first indexing and the fact that 60%+ of web traffic is mobile. Mobile LCP failures typically stem from different root causes than desktop: slower CPUs increase Phase 4 (render delay), smaller viewports mean different LCP elements, and mobile networks mean longer Phase 3 (resource download). Fix mobile first, then verify desktop still passes.
Q: I added a preload tag and my LCP improved in lab data but field data hasn’t changed. Why?
Field data uses CrUX’s 28-day rolling window. Lab improvements show immediately; field data updates take 2–4 weeks as new (improved) visits accumulate and older (poor) visits age out. Also verify the preload tag is being served correctly — check the HTML source in an incognito window to confirm the tag is in <head>.
Q: Should I preload the hero image on every page or just the homepage?
Preload the LCP image on every page where there’s a prominent above-the-fold image. This typically includes: homepage, category landing pages, product pages, key blog posts. The preload tag should be dynamically added per-template if your CMS uses different hero images per page.
Q: My TTFB is fast (200ms) but LCP is still 3.5s. Where’s the delay?
With fast TTFB, the delay is in Phases 2–4. Investigate: (1) render-blocking resources in <head> delaying LCP discovery, (2) missing preload for the LCP image, (3) image file size too large, (4) LCP image being lazy-loaded, (5) JavaScript executing on the main thread and blocking Phase 4 render. Use WebPageTest filmstrip to see exactly when the LCP element appears.
Q: Does image lazy loading affect LCP for non-LCP images?
No — lazy loading should be applied to all images except the LCP image. In fact, lazy loading non-LCP images is beneficial: it reduces network congestion, ensuring the LCP image download gets more bandwidth. The correct pattern: no lazy loading on the first 1–2 above-fold images, loading="lazy" on everything below the fold.
Q: Is there a Shopify-specific way to preload the LCP image?
In Shopify themes (Dawn and OS2.0 themes), you can modify the section template for your hero banner. In the <head> of theme.liquid, add: {%- if section.settings.image -%}<link rel="preload" href="{{ section.settings.image | image_url: width: 1200 }}" as="image" fetchpriority="high">{%- endif -%}. The exact Liquid tags depend on your theme’s variable naming.
Conclusion
LCP optimization is a systematic process: identify the element, measure the four phases of its waterfall, and fix the slowest phase first. In most cases, the biggest wins come from server-side improvements (TTFB) and correct resource hinting (preload + fetchpriority), not from image compression alone.
The five-minute quick wins that move the needle most: remove loading="lazy" from your LCP image, add fetchpriority="high" to the LCP image element, add a <link rel="preload"> tag in <head>, and convert the image to WebP. Those four changes alone often improve LCP by 500ms–1.5 seconds.
For deeper LCP fixes — TTFB improvements across hosting configurations, custom srcset implementations, or CDN setup — the Ignited Nepal team delivers full-stack performance engineering with measurable before/after results.
→ Work with Ignited Nepal’s Web Performance team
Written by the Ignited Nepal team. ignitednepal.com