ESC

Type to search the knowledge base.

LCP Optimization Tactics

Deep tactics for Largest Contentful Paint — discovery, priority, bytes, server delay, and render delay — beyond the CWV overview.

intermediate5 min read
  • 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

  1. Modern formats: AVIF / WebP with fallbacks via <picture>.
  2. Correct dimensions: don’t ship 4000px into a 400px slot.
  3. Compression tuned per page type (hero vs thumbnail).
  4. CDN + HTTP cache; immutable hashed URLs.
  5. 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:

  1. Render-blocking CSS — huge bundles, unused framework CSS (critical rendering path).
  2. Parser-blocking JS in head.
  3. Font loading delaying text LCP — font-display, subset, preload one face.
  4. Main-thread congestion — long tasks before first paint; hydration walls on heavy SPAs.
  5. 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

  1. Prefer SSR/SSG for the LCP route shell.
  2. Don’t fetch hero CMS data only on the client for first paint.
  3. Hydration JS competes for bandwidth and CPU — code-split below-fold islands.
  4. Third-party tags: load after LCP when contracts allow.
  5. Client routers: soft navigations have different LCP nuances; still optimize landing documents hard.

Checklist you can ship against

  1. Confirm LCP element in RUM for top templates.
  2. URL present in initial HTML or matching preload.
  3. fetchpriority="high", not lazy.
  4. Sized + modern format + CDN.
  5. Preconnect cross-origin CDNs.
  6. CSS/JS critical path trimmed (CRP, pipeline).
  7. TTFB budget owned by backend/platform.
  8. 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.

Further reading

Related guides