Modern HTML5 in Production · Loading Performance
Images, LCP and Layout Shift
Why a marketing page felt slow and jumpy after an 'optimisation' that lazy-loaded every image, and how dimensions and loading priorities fix LCP and CLS.
Scenario
Example app: to speed up a landing page, a developer adds loading="lazy" to every image, including the large hero image at the top. Images have no width or height attributes because "CSS sizes them".
What you see in production
- Largest Contentful Paint (LCP), the time until the main content shows, gets worse, not better.
- The text jumps down when images arrive; users tap the wrong button (Cumulative Layout Shift, CLS, is poor).
- Search Console flags the page's Core Web Vitals as "needs improvement".
The naive code
<img src="hero.jpg" alt="Hikers at sunrise" loading="lazy" class="hero">
<p>Book your trip today...</p>
<img src="gallery-1.jpg" alt="..." loading="lazy">
Reproduce it
The model estimates when the hero image finishes loading, and how far content shifts, for three versions of the page.
const page = { htmlMs: 300, cssMs: 250, layoutMs: 100, heroDownloadMs: 600 };
function heroLcp({ lazy, priority }) {
// the browser discovers the image while parsing HTML (preload scanner); lazy images wait for layout first
const start = lazy ? page.htmlMs + page.cssMs + page.layoutMs : page.htmlMs + (priority ? 0 : 100);
return start + page.heroDownloadMs;
}
function layoutShift({ dimensions }) {
// without width/height the browser reserves 0px, then pushes text down by the image height when it arrives
return dimensions ? 0 : 480;
}
const versions = {
"lazy hero, no dimensions": { lazy: true, priority: false, dimensions: false },
"eager hero, no dimensions": { lazy: false, priority: false, dimensions: false },
"eager + fetchpriority=high + width/height": { lazy: false, priority: true, dimensions: true },
};
for (const [label, v] of Object.entries(versions)) {
console.log(`${label.padEnd(42)} hero visible ~${heroLcp(v)} ms, content pushed down ${layoutShift(v)} px`);
}
Output
lazy hero, no dimensions hero visible ~1250 ms, content pushed down 480 px eager hero, no dimensions hero visible ~1000 ms, content pushed down 480 px eager + fetchpriority=high + width/height hero visible ~900 ms, content pushed down 0 px
Root cause
- Lazy loading delays an image until the browser knows it's near the viewport, which requires CSS and layout first. For the hero image, which is always in view, that only adds delay to LCP.
- Without
widthandheight, the browser doesn't know the image's size before it downloads, so it reserves no space and shifts the content later (CLS).
The fix
- Hero and other above-the-fold images: no
loading="lazy"; addfetchpriority="high"to the single most important image. - Below-the-fold images and iframes:
loading="lazy". - Always set
widthandheightto the image's intrinsic size (or use CSSaspect-ratio) and let CSS scale it (max-width: 100%; height: auto). - Make sure the hero is in the HTML (not injected by JavaScript or a CSS background), so the browser discovers it early. More in optimising LCP.
Trade-offs
fetchpriority="high"on many images cancels its benefit; use it for one.- Placeholders (blurred previews, dominant colours) improve perceived speed but add complexity.
Detect it in production
- Real-user monitoring of LCP and CLS (the
web-vitalslibrary, Search Console's Core Web Vitals report). - Lighthouse in CI flags lazy-loaded LCP images and images without dimensions.
Key points
- Never lazy-load the LCP (hero) image; prioritise it instead.
- Always give images
widthandheight(oraspect-ratio) to prevent layout shift. - Lazy-load only what's below the fold.
Practice questions
Why can lazy-loading the hero image make a page slower?
Answer
Lazy images wait until the browser has CSS and layout to know they're near the viewport, so the most important image starts downloading later, which worsens Largest Contentful Paint. Load it eagerly, ideally with fetchpriority="high".
How do width and height attributes prevent layout shift?
Answer
They give the browser the image's aspect ratio before it downloads, so it reserves the right space; CSS can still scale the image. Without them, content jumps when the image arrives.
What are the Core Web Vitals?
Answer
Largest Contentful Paint (loading), Interaction to Next Paint (responsiveness) and Cumulative Layout Shift (visual stability), measured from real users.
Open in the app to mark lessons complete, take notes and track your progress.