On most content and commerce pages the Largest Contentful Paint element is one image. Fix how that image is chosen, sized, prioritised and delivered and the score moves, whatever framework rendered the page around it. Almost none of those decisions have to live in a template.
Know which element you are actually optimising
The metric only considers a short list of elements, which was kept deliberately small. Images, an image inside an svg, video elements, an element with a background image loaded through url(), and block level elements containing text. Good is 2.5 seconds or less, judged at the 75th percentile of page loads, with mobile and desktop measured separately.
One detail in that definition pays for itself immediately. For an image resized from its natural dimensions, the size credited is the visible size or the intrinsic size, whichever is smaller. Shipping a 3000 pixel wide hero into an 800 pixel slot buys you nothing in the metric and costs your visitor the difference in bytes.
Split the number before you tune it
web.dev breaks LCP into four parts with rough targets. Time to first byte at around 40 percent, resource load delay under 10 percent, resource load duration at around 40 percent, and element render delay under 10 percent.
Measure the split before you touch anything. A slow origin and a bloated hero image are both common, they need opposite fixes, and the four part breakdown tells you which one you have in about a minute.
Negotiate the format at the edge
Browsers already tell you what they can decode. A current browser requesting an image sends an Accept header along the lines of image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5, so the server can pick the format without any markup change at all.
# /etc/nginx/conf.d/avif-negotiation.conf
map $http_accept $avif_suffix {
default "";
"~*image/avif" ".avif";
}
# inside the server block that serves your media
location ~* ^/media/.+\.(jpe?g|png)$ {
add_header Vary Accept;
try_files $uri$avif_suffix $uri =404;
}
You pre-generate hero.jpg.avif next to hero.jpg, and a browser that advertises AVIF gets it. When the map yields an empty string the first candidate is just the original path, so the fallback needs no extra branch.
Two things make or break this. Check that your nginx ships the AVIF type with grep avif /etc/nginx/mime.types, because without it the file goes out with the wrong content type. And keep the Vary header, since it is what tells every cache between you and the visitor that this URL has more than one correct answer. The same Vary value has to appear on every response for that URL, including 304 responses.
Give priority to exactly one image
Images start life at low priority and are raised to high at layout time if they turn out to be in the viewport. That promotion is real work that happens after layout, and it is time your hero spends waiting.
<img src="/media/hero.jpg" width="1200" height="675" fetchpriority="high" alt="">
The fetchpriority attribute takes high, low or auto, and high on the LCP image is the documented use. The discipline is the point. Mark one image high per page, and consider low for the carousel slides that are not on screen yet, so the browser spends its first connections on the one that counts.
If the image is already preloaded as one of the first things in the head, adding high priority to the tag will not change much on top of it. Preloaded images should carry fetchpriority="high" on the preload itself, otherwise the preload inherits the default priority for its as value.
The attribute that quietly ruins a hero
Lazy loading is the single most common self-inflicted LCP wound, usually installed by a plugin that applies it to every image on the page. The guidance from web.dev is direct. Do not lazy load images likely to be in the viewport at load, especially the LCP image, and use loading="lazy" only for images outside the initial viewport.
Two supporting rules travel with it. Always set width and height, since an image with no dimensions is laid out as zero by zero, which both invites layout shift and can convince the browser that an entire gallery fits on screen. And for images you do lazy load, sizes="auto" lets the browser use the element's own laid out width to pick from srcset, which saves maintaining the same breakpoints twice. It is valid only alongside loading="lazy", and where it cannot be resolved the browser falls back through the rest of the sizes list.
When the hero lives in CSS
A background image set in a stylesheet is invisible to the preload scanner, so the browser cannot start it until the CSS has been fetched and applied. That is pure load delay, the subpart that should be under 10 percent. Preload it from the head instead.
<link rel="preload" as="image" href="/media/hero-800.avif"
imagesrcset="/media/hero-400.avif 400w, /media/hero-800.avif 800w, /media/hero-1600.avif 1600w"
imagesizes="100vw" fetchpriority="high">
Preload the format you most want served rather than one link per format, and let browsers without support fall through to the normal path.
Prove it from the command line
# does the edge actually negotiate, and does it say so to caches
curl -sI -H 'Accept: image/avif,image/webp,image/*;q=0.8' \
https://example.com/media/hero.jpg | grep -i 'content-type\|vary\|cache-control'
# and the same URL for a client that cannot take AVIF
curl -sI -H 'Accept: image/png,image/*;q=0.8' \
https://example.com/media/hero.jpg | grep -i 'content-type'
Two responses, two content types, one Vary: Accept on both. If the second request also returns AVIF, something upstream is ignoring the negotiation and a share of your visitors are getting an image they cannot render.
Most of this survives a theme upgrade precisely because it never entered the theme. If you want the delivery layer built and measured rather than guessed at, that is what our server setup and optimization work does, and monitoring and observability is how you keep the number honest once it drops. For the field data side of the same problem, see measuring real-world performance beyond Lighthouse.
Talk to the engineer who will own your stack.
No account managers, no offshore handoff. Senior DevOps, direct. Tell us what you are dealing with and you get a straight answer.
Related Articles
The Ultimate Guide to Linux Server Management in 2025
A comprehensive guide to modern Linux server management covering automation, containerization, cloud integration, AI-driven operations, security best practices, and essential tooling for 2025.
Server & DevOpsFixing "421 Misdirected Request" for Plesk Sites on Ubuntu 22.04 After Apache Update
Resolve the 421 Misdirected Request error affecting all HTTPS sites on Plesk for Ubuntu 22.04 after an Apache update, caused by changed SNI requirements in the nginx-to-Apache proxy chain.
Server & DevOpsHow to Set Up GlusterFS on Ubuntu
A complete guide to setting up a distributed, replicated GlusterFS filesystem across multiple Ubuntu 22.04 nodes, including installation, volume creation, client mounting, maintenance, and troubleshooting.