LCP Optimization Tactics
Deep tactics for Largest Contentful Paint — discovery, priority, bytes, server delay, and render delay — beyond the CWV overview.
- performance
- lcp
- web-vitals
- images
LCP (Largest Contentful Paint) marks when the largest above-the-fold content element becomes visible. Good: ≤ 2.5s at the 75th percentile (field). This page is a tactics manual — assume you already know the metric definition from Core Web Vitals. Here we break LCP into subparts and attack each one.
Docs: web.dev LCP, Optimize LCP, LCP API.
What can be the LCP element?
Common winners:
<img>(including poster-like images)- Elements with CSS background images are not LCP candidates the same way — prefer real
<img>/<picture>for heroes - Block-level text nodes (headlines)
<video>poster frames in some cases
Identify first. In lab: Performance panel / Lighthouse “LCP element.” In field: web-vitals attribution / RUM. Optimizing a decorative banner while LCP is actually the H1 is wasted work.
import { onLCP } from 'web-vitals';
onLCP((metric) => {
console.log(metric.value, metric.attribution);
});
The four time buckets
Chrome’s LCP breakdown (conceptual):
| Subpart | Meaning | Levers |
|---|---|---|
| TTFB | Document arrival | CDN, cache, SSR cost, origin |
| Resource load delay | Time until LCP resource starts | Discovery, priority, contention |
| Resource load duration | Download of LCP bytes | Size, format, CDN, compression |
| Element render delay | After resource ready until paint | Render-blocking CSS/JS, main thread |
Text LCP may skip “resource load” and still suffer TTFB + render delay. Image LCP usually hits all four.
Tactic 1 — Discover the LCP resource in HTML
Bad: hero URL only after React hydrate + useEffect fetch.
Good: server-render the <img src> or preload in initial HTML.
<!-- In document head when the hero URL is known -->
<link
rel="preload"
as="image"
href="https://cdn.example.com/hero.avif"
fetchpriority="high"
imagesrcset="…"
imagesizes="…"
/>
For responsive images, preload must match the resource the browser will actually select (imagesrcset / imagesizes on the preload). Mismatched preloads waste bandwidth and miss LCP.
Frameworks: Next.js <Image> + priority prop, or plain SSR markup. Client-only CMS hero injection is a frequent regression.
Tactic 2 — Prioritize, don’t lazy-load the hero
<img
src="/hero.avif"
width="1200"
height="630"
alt=""
fetchpriority="high"
decoding="async"
/>
Checklist:
- No
loading="lazy"on LCP fetchpriority="high"on the single LCP candidate- Avoid competing high-priority images above the fold
- Preconnect the CDN origin if cross-origin:
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
Tactic 3 — Cut bytes without killing quality
- Modern formats: AVIF / WebP with fallbacks via
<picture>. - Correct dimensions: don’t ship 4000px into a 400px slot.
- Compression tuned per page type (hero vs thumbnail).
- CDN + HTTP cache; immutable hashed URLs.
- Consider priority of quality vs LCP on mobile — art direction with
<picture>can swap a lighter crop.
<picture>
<source type="image/avif" srcset="/hero.avif" />
<source type="image/webp" srcset="/hero.webp" />
<img src="/hero.jpg" width="1200" height="630" alt="" fetchpriority="high" />
</picture>
Reserve space (width/height or aspect-ratio) so LCP work doesn’t create CLS.
Tactic 4 — Attack render delay
Resource finished, but LCP still late? Something blocks paint:
- Render-blocking CSS — huge bundles, unused framework CSS (critical rendering path).
- Parser-blocking JS in head.
- Font loading delaying text LCP —
font-display, subset, preload one face. - Main-thread congestion — long tasks before first paint; hydration walls on heavy SPAs.
- Client-only rendering of the LCP node — SSR/stream the text or image markup.
Streaming SSR helps: send shell + hero early, stream the rest. In React/Next, avoid wrapping the entire LCP subtree behind a single slow data gate if the hero data is already available.
Tactic 5 — Server and document path
- Edge-cache public HTML where personalization allows.
- Shrink middleware and auth checks on the HTML critical path.
- HTTP/2 or HTTP/3; avoid extra redirects on the document or LCP URL.
- Early Hints (
103) can help preconnect/preload on supporting stacks — measure; not a substitute for good HTML.
TTFB regressions from “we added one more API to the document request” show up as LCP regressions even when images are perfect.
Tactic 6 — When text should win LCP
If the largest paint is a hero image that is hard to optimize (third-party, UGC), a large headline as LCP can be faster and still meaningful. Design and SSR text so the H1 paints early; ensure the image isn’t accidentally larger and slower without priority.
This is a product/design conversation, not only an image CDN ticket.
Lab vs field traps
| Trap | Reality |
|---|---|
| Lighthouse green on desktop cable | Mobile 75th p95 users still fail |
| Optimized wrong element | Always verify LCP node in field |
| Preload everything | Starves the real LCP resource |
| Lazy-load “below fold” incorrectly | Hero still lazy on small viewports |
| Ignoring bfcache restores | Separate path; still watch UX |
Measure 75th percentile by form factor. Use CrUX + your RUM. Lab is for debugging waterfalls, not for declaring victory.
SPA and hydration specifics
- Prefer SSR/SSG for the LCP route shell.
- Don’t fetch hero CMS data only on the client for first paint.
- Hydration JS competes for bandwidth and CPU — code-split below-fold islands.
- Third-party tags: load after LCP when contracts allow.
- Client routers: soft navigations have different LCP nuances; still optimize landing documents hard.
Checklist you can ship against
- Confirm LCP element in RUM for top templates.
- URL present in initial HTML or matching preload.
fetchpriority="high", not lazy.- Sized + modern format + CDN.
- Preconnect cross-origin CDNs.
- CSS/JS critical path trimmed (CRP, pipeline).
- TTFB budget owned by backend/platform.
- Regression tests: Lighthouse CI on key URLs + field dashboards.
Interview angle
Define LCP + 2.5s threshold. Decompose into TTFB / delay / duration / render delay with one fix each. Emphasize discovery in HTML and not lazy-loading the hero. Mention field 75th percentile.
Related on this site
- Core Web Vitals — metric definitions and thresholds
- Critical rendering path — first-load blocking chain
- Browser rendering pipeline
- INP optimization tactics
- Picture and srcset
- HTTP caching headers
Further reading
- web.dev: Optimize Largest Contentful Paint
- web.dev: LCP
- web.dev: Fetch Priority
- web.dev: Preload responsive images
- MDN: Largest Contentful Paint API
Related guides
- CLS Optimization TacticsFix cumulative layout shift: dimensions, font metrics, reserved slots, and stable late-loading UI.
- Image Optimization FormatsAVIF, WebP, JPEG, PNG: choose formats, quality, and tooling so LCP images stay sharp and small.
- Core Web VitalsLCP, INP, and CLS — what field data measures, good thresholds, and fixes that actually move the needle (per web.dev guidance).
- Code Splitting StrategiesRoute, component, and vendor splits: dynamic import(), magic comments, and avoiding over-splitting waterfalls.
- Critical CSS ApproachesInline above-the-fold CSS, extract critical rules, and avoid shipping megabyte stylesheets on first paint.