Back to Blog
PerformanceJuly 10, 20264 min read

LCP Optimization: Async Decoding and CSS Containment for Hero Images

Master LCP optimization by combining async image decoding and CSS containment. Learn to stabilize your hero section and improve your web performance scores.

LCPWeb PerformanceCSSFrontendOptimizationPerformanceWeb Vitals

When you’re staring at a Lighthouse report and seeing an LCP (Largest Contentful Paint) score that won't budge, your hero section is usually the culprit. I recently spent about two days refactoring a client's landing page where the background images were causing massive main-thread blocking during the initial render.

The fix wasn't just about compressing the image; it was about how the browser handles the work of painting that image to the screen.

Why Hero Images Stall Your LCP

Browsers are smart, but they're also lazy. By default, when an image is loaded, the browser decodes it on the main thread. If you have a large hero background, that decoding process blocks the browser from doing anything else—like processing CSS or handling user interactions.

If you're already familiar with the basics, you might have read my guide on LCP optimization: Using Fetchpriority and Preloading for Hero Images. While preloading tells the browser what to fetch, image decoding and CSS containment tell the browser how to handle the rendering, which is just as vital for a smooth LCP optimization strategy.

Implementing Accelerated Image Decoding

The decoding="async" attribute is a game-changer. It tells the browser, "Don't wait for this image to decode before you show the rest of the page."

HTML
style="color:#808080"><style="color:#4EC9B0">img src="hero-banner.webp" 
     alt="Hero background" 
     decoding="async" 
     fetchpriority="high">

When you use decoding="async", the browser offloads the decoding process to a background thread. This keeps your main thread free to handle critical tasks, which is essential if you're trying to keep your web performance metrics green.

Using CSS Containment to Protect Layout

Even with fast decoding, your hero section can trigger expensive layout recalculations if the browser doesn't know the dimensions upfront. This is where CSS containment comes in. It tells the browser that the internal layout of an element is independent of the rest of the page.

Using contain: layout size; (or the shorthand contain: content;) essentially sandboxes the hero area. If you're struggling with layout stability, you can also check out my notes on Cumulative Layout Shift optimization: Mastering CSS Containment for more context on how this prevents UI jitter.

CSS
#9CDCFE">color:#4EC9B0">.hero-container {
  #9CDCFE">color:#6A9955">/* Isolates the hero from the rest of the DOM */
  contain: layout size;
  #9CDCFE">aspect-ratio: 16 / 9;
  #9CDCFE">width: 100%;
  #9CDCFE">overflow: hidden;
}

The "Wrong Turn" We Took

Initially, we tried using will-change: transform on the entire hero section to force GPU acceleration. It seemed like a good idea, but it actually caused the browser to create extra layers, which increased memory pressure on mobile devices and actually hurt our LCP score by roughly 150ms.

We stripped that out and stuck to simple, declarative CSS containment. The improvement was immediate. By letting the browser handle the painting efficiently rather than forcing its hand, we stabilized the rendering process.

Summary of Performance Techniques

TechniquePrimary BenefitBest For
decoding="async"Offloads main threadLarge hero images
contain: layoutPrevents layout reflowComplex hero containers
aspect-ratioStops layout shiftsResponsive images
fetchpriorityPrioritizes network loadImmediate LCP assets

Closing Thoughts

Performance is rarely about one "silver bullet" fix. It’s about removing the friction points that prevent the browser from doing its job. I’m still experimenting with the new content-visibility: auto property for sections below the fold, but for your hero section, decoding="async" and strict CSS containment remain my go-to combination for reliable, fast rendering. If you're building a modern site and need help with these patterns, I offer Next.js Website & Landing Page Development to handle these optimizations from the start.

FAQ

1. Does decoding="async" affect the quality of the image? No. It only changes when the image is decoded. The final visual output remains identical to a synchronous decode.

2. Can I use contain: size on every element? Be careful. contain: size forces the element to respect the dimensions you set in CSS. If you don't explicitly set a height, the element might collapse to zero height.

3. Is this better than just using a background-image in CSS? For LCP, an <img> tag is almost always better. It allows the browser to use modern features like fetchpriority and decoding="async", which CSS background images don't support as effectively.

Similar Posts