Largest Contentful Paint (LCP) fails when the browser is still waiting on the server, fighting for bandwidth, or blocked on JavaScript before it can paint the biggest thing in the viewport. You do not need a six-week performance programme to move the needle. In our delivery work, four changes cover most urgent LCP regressions: fix Time to First Byte (TTFB), remove competing work from the path to the LCP element, prioritise that element with fetchpriority="high", and schedule low-value JavaScript after the load event. What follows is a practical order of operations, with enough detail to implement without re-reading a full Core Web Vitals textbook.
For background on thresholds and the four LCP sub-parts, see LCP, INP, CLS: What Each Core Web Vital Means and How to Fix It. For image-specific tuning when the LCP element is a hero photo, see Image Optimisation Strategies for Better LCP Scores. Both posts go deeper on sub-parts and formats; here we stay focused on the fastest fixes.
What is the path to LCP?
LCP is not one timer. It is a chain from first request to final paint of the largest visible element. Google’s documentation breaks the chain into TTFB, resource load delay, resource load duration, and element render delay. The path to LCP is everything the browser does before that element paints: HTML download, discovery of the LCP resource, competing requests, font and script work on the main thread, and layout.
When teams say “LCP is slow but the hero image is only 200 KB,” the bottleneck is usually earlier in the chain. TTFB adds blank time. Icons, analytics, web fonts, and below-the-fold images steal connection slots. JavaScript runs before the browser can commit the LCP paint even after the asset has arrived. The four fixes below map directly to those failure modes.
Step 1: Improve TTFB (Time to First Byte)
TTFB is pure page-blank time. Nothing in the viewport can render until the first byte of HTML arrives. If TTFB is 800 ms on mobile lab tests, every downstream optimisation starts late.
Treat TTFB as a caching and origin problem first:
- Cache database queries on CMS and ecommerce routes that build the HTML shell. Object caches (Redis, Memcached) and query result caching cut repeated work on popular templates.
-
Cache full pages or page fragments at the edge. A CDN or reverse proxy (Cloudflare, Fastly, Varnish, nginx
proxy_cache) should serve anonymous HTML for marketing and category pages where content is not user-specific. - Cache generated code paths where applicable. Opcode caches (OPcache on PHP), template compilation caches, and warmed application caches reduce CPU time per request.
Also check the basics: slow upstream APIs in the critical path, cold serverless instances, and TLS or DNS misconfiguration. TTFB improvements often drop LCP by hundreds of milliseconds without touching front-end assets at all, which makes this step worth doing even when the design team is already compressing heroes.
Run PageSpeed Insights and note TTFB in the diagnostics. If it is high, fix origin and caching before optimising images. Our Core Web Vitals guide lists the “good” LCP band at 2.5 seconds or less in the field; shaving TTFB is the fastest way to buy headroom when you are close to the line.
Step 2: Remove anything in the path to LCP that matters less than the LCP element
Once HTML starts arriving, the browser schedules dozens of requests. Anything that competes with the LCP resource for bandwidth or main-thread time extends resource load delay and render delay. The rule is simple: if it is not required to paint the LCP element, it should not run or download ahead of it.
Icons and decorative assets
Favicons, sprite sheets, and small UI icons often sit early in the document or load from high-priority stylesheets. They rarely affect LCP directly, but they consume connections and parser attention. Inline critical icon SVGs only when needed above the fold; otherwise load icon fonts and sprite bundles after the LCP resource is underway or from low-priority async paths.
Third-party scripts
Analytics, chat widgets, A/B testing snippets, and personalisation tags are common LCP killers. They execute in the head or early body and block parsing. Audit with the Network panel: sort by start time and see what fires before the LCP image or text block.
Practical moves:
- Load tag managers and non-essential vendors after the LCP resource is discoverable (see Step 4 for
load-based scheduling). - Self-host or proxy only what you truly need on first paint.
- For deeper triage patterns, see Third-Party Scripts and Performance: How to Identify and Fix the Worst Offenders.
Below-the-fold images
Lazy-loading is correct for content lower on the page, but never lazy-load the LCP candidate. Conversely, do not let below-the-fold <img> tags without loading="lazy" sit in the head of the queue with fetchpriority defaults that compete with the hero. Ensure decorative and content images that are not LCP use lazy loading and lower priority so the hero wins the network race.
Fonts that are not needed for the LCP element
Web fonts are often requested from CSS before the LCP image is even parsed. If the LCP element is a photograph, decorative display fonts do not need to block the chain. If the LCP element is a headline, only the weights used in that headline need early loading.
The goal is not “delay until interaction” tricks that hide text until a user taps. The goal is enqueue order: make sure font files for secondary UI copy enter the pipeline after the LCP resource is requested and ideally after it has started downloading. Techniques include splitting @font-face rules into non-critical stylesheets loaded later, subsetting fonts (font subsetting guide), and using system font stacks for non-LCP text on first paint.
Step 3: Prioritise the LCP element with fetchpriority="high"
Browsers schedule requests with heuristics. Hero images discovered late in HTML or hidden behind CSS may get default priority while less important assets jump ahead. The fetchpriority attribute lets you mark what matters.
For an image LCP element:
<img
src="/media/hero.avif"
alt="Product range on a workbench"
width="1200"
height="675"
fetchpriority="high"
/>
Pair with <link rel="preload" as="image" href="/media/hero.avif" fetchpriority="high"> in <head> when the LCP image is known at build time and appears late in the markup. Preload is not a substitute for fixing TTFB or removing competing scripts, but it removes discovery delay when the hero sits below inline styles or markup generated by JavaScript.
Do not set fetchpriority="high" on everything. If every image is high priority, none are. Reserve it for the single LCP candidate PSI reports in Lighthouse diagnostics.
For text or video LCP elements, the same principle applies: ensure the resource that feeds the largest paint is discoverable early and not deprioritised by lazy attributes or late injection.
Step 4: Schedule less important JavaScript after the load event
Even when the LCP asset has downloaded, element render delay can balloon if the main thread is busy executing JavaScript. Parsing, compilation, and long tasks defer layout and paint, which is why deferral belongs in the same checklist as caching and fetch priority.
defer and async help for scripts that must run before interaction, but many apps bundle analytics, carousels, and enhancement code that can wait until the page has fully loaded. The load event fires when the document and its subresources (images, stylesheets) have finished loading. Scheduling non-critical work there keeps the main thread freer while LCP is still forming.
Example pattern:
<script>
window.addEventListener('load', function () {
requestIdleCallback(function () {
initNonCriticalWidgets();
loadDeferredAnalytics();
}, { timeout: 2000 });
});
</script>
Use requestIdleCallback where supported so you do not immediately replace one long task with another. Framework-specific code splitting (dynamic import() on load or on first interaction) achieves the same outcome: the LCP element paints before megabytes of client-side routing and chart libraries execute.
This is distinct from deferring fonts until interaction, which can harm readability. Here we defer JavaScript functions that do not affect first paint: heatmaps, chat bubbles, personalisation hooks, and secondary carousels below the hero.
A one-hour LCP fix workflow
Use this sequence when you have a red LCP on a priority URL and limited time:
- Identify the LCP element in PageSpeed Insights or Lighthouse (Diagnostics → LCP element).
- Record TTFB on mobile lab. If it is above ~600 ms, open caching and origin tickets first.
- Waterfall audit: list every request and script that starts before the LCP resource. Remove or postpone icons, third parties, lazy-competing images, and non-LCP fonts.
-
Add
fetchpriority="high"(and preload if needed) on the LCP node only. -
Move non-critical JS to
load+ idle callbacks; retest. - Set an LCP budget in your monitoring tool so the fix survives the next deploy. See Performance Budget Thresholds Template.
Agencies managing many client homepages and landers should run mobile and desktop lab tests on the same URL after each change. One-off PSI tabs miss regressions when a plugin reintroduces a head script. PageSpeed Insights vs automated monitoring explains why scheduled tests and alerts matter once the quick fix ships.
What these four steps do not replace
These changes target the delivery path to LCP. They do not replace image compression, responsive srcset, CDN tuning, or server-side rendering when the LCP element itself is too large or injected only after client-side hydration. When the LCP element is an image, combine this checklist with the image optimisation guide. When third parties dominate the waterfall, the third-party scripts post goes deeper on vendor policy.
LCP also differs between mobile and desktop strategies. A fix on one may not carry to the other if different heroes or ad slots apply. Monitor both; see Mobile vs Desktop Core Web Vitals.
FAQ
Should I fix TTFB or the LCP image first?
Fix TTFB first when it is high. Blank time before HTML cannot be recovered by compressing a hero image. If TTFB is already healthy, focus on the path to LCP and the LCP resource itself.
Does fetchpriority replace preload?
No. fetchpriority="high" adjusts priority once the browser discovers the resource. Preload starts discovery earlier when the URL is known at build time. Use both on image heroes that appear late in the document.
Is lazy-loading the LCP element ever correct?
No. Never lazy-load the element PageSpeed Insights reports as LCP. Lazy-load below-the-fold images only.
Why defer JavaScript to load instead of using defer on every script?
The defer attribute preserves execution order before DOMContentLoaded for scripts that still run during the critical path. Analytics, widgets, and enhancement bundles that do not affect first paint can wait until load, which reduces main-thread contention while LCP is still forming.
How do I know the fix worked?
Re-run PageSpeed Insights on the same URL and strategy (mobile vs desktop). Compare LCP time and the filmstrip. For production confidence, schedule lab tests with budgets in Apogee Watcher so the next theme or tag-manager change triggers an alert if LCP drifts back above your threshold.
Can Apogee Watcher fix LCP automatically?
No. Watcher schedules PageSpeed lab tests, stores LCP and other vitals over time, and emails you when budgets breach. You still implement caching, prioritisation, and script deferral in the codebase; Watcher tells you when those fixes slip on priority URLs across client sites.
TTFB, a clear path to the LCP element, fetchpriority="high", and JavaScript deferred until after load are the four changes we reach for first when LCP is red and the release window is small. Ship them in that order, re-test on mobile lab, then lock the win with budgets and scheduled monitoring so the metric stays green after the next deploy. Start a free Apogee Watcher account to track LCP on priority URLs with mobile and desktop schedules and vitals budgets, or run a free domain PageSpeed check before you change caching or script order.
Top comments (0)