Lazy Loading vs. Eager Loading: How to Decide What Should Wait
Rule of thumb: make the first meaningful thing arrive fast, and make everything else wait its turn — the trick is knowing which is which.
The Jargonless Difference
Eager loading yells at the browser, “hey, fetch this now!” This is how you treat your hero image, critical CSS, and any asset that defines your LCP or blocks interaction. Lazy loading whispers to the browser, “fetch this when it’s almost needed...take your time...” It’s how you treat off-screen images, below-the-fold videos, and third-party embeds; why bother loading what is not yet being seen?
Why it matters: Core Web Vitals (those metrics Google cares about when ranking your site). Eager-loading the wrong things balloons network pressure and delays LCP, resulting in dismal performance scores; lazy-loading the wrong things creates “pop-in,” input delay, or SEO blind spots. This isn’t a religious war between two attributes — it’s a prioritization problem that you solve per resource.
If you want the receipts, here are excellent references:
MDN on loading
for images/iframes,
web.dev on lazy-loading images,
Chrome Developers on Priority Hints (fetchpriority
),
W3C preload, and
Google Search Central on lazy-loading for SEO.
A Nimble Mental Model: Budget, Priority, and Going the Distance
The most useful analogy I can think of is to have the mentality of an air-traffic controller. You have a limited runway (i.e., bandwidth and CPU), a queue (parser + preload scanner), and different aircraft with different urgency and needs. Act to Improve:
- Budget: Early milliseconds are the most valuable, so spend them on what paints fast — it is an easy and effective win
- Priority: If it defines the first impression, boost it...If it’s decoration, defer it.
- Distance: The further from the fold a media asset sits, the more aggressively you can lazy-load. Trust me, users will not scroll half as fast as you think.
Fun fact: browsers already automatically prioritize, but they’re guessing from context of code elements. Developers, however, can turn that guess
into an educated assumption for the browser with a few simple nudges: loading
, fetchpriority
, and
rel="preload"
. Used together, they make your intent unmistakable (yes, even to Safari).
Timing Eager Loading and Effective implementation
Use eager loading for assets that affect above-the-fold rendering and early interactions (in this order):
- Hero (LCP) image: the massive, striking image users see first.
- Critical User Interface (UI) icons/illustrations: they tend to be small but visually-blocking items.
- Critical CSS: inlined or preloaded; don’t lazy this.
- Fonts that paint above the fold: preload selectively to avoid conflicts.
Hero image example
<!-- In <head>: give the browser a head start -->
<link rel="preload"
as="image"
href="/assets/images/hero@2x.avif"
imagesrcset="/assets/images/hero.avif 1x, /assets/images/hero@2x.avif 2x"
imagesizes="(min-width: 1024px) 960px, 100vw">
<!-- In body: explicitly mark assets as high priority and declare sizes to avoid CLS (snapping) -->
<img
src="/assets/images/hero.avif"
srcset="/assets/images/hero.avif 1x, /assets/images/hero@2x.avif 2x"
sizes="(min-width: 1024px) 960px, 100vw"
width="1920" height="1080"
alt="Your product in context"
fetchpriority="high"
decoding="async"
loading="eager" />
Notes: fetchpriority="high"
nudges the network scheduler for that single
hero image (see
Priority Hints).
Always declare width
and height
to prevent layout shift.
Use modern formats (AVIF/WebP) and an honest sizes
value.
CSS backgrounds as heroes?
It is important to note that background images can’t use loading
or fetchpriority
directly, so be sure to
preload them:
<link rel="preload" as="image" href="/assets/images/hero-bg.avif">
The When of Lazy Loading (Save Bandwidth, Save Sanity, Save Yourself)
Lazy-load anything that is not needed to render the first view:
- Gallery images below the fold — blog inline images after the intro (it is worth it)
- Embedded maps, videos, social widgets, ad slots, and analytics extras
- Code for features users may never trigger (tooltips, carousels, charts)
Image block
<img
src="/assets/images/case-study-1.webp"
width="1200" height="800"
alt="Results dashboard for client A"
loading="lazy"
decoding="async"
/>
Iframe block (maps, video)
<div class="aspect-video rounded-lg ring-1 ring-black/10 overflow-hidden">
<iframe
src="https://www.youtube-nocookie.com/embed/XXXXXXXX"
title="Feature demo"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen></iframe>
</div>
For SEO, Google can index lazy-loaded images when implemented with standard markup. Avoid custom JS that hides content from crawlers. See Google’s guidance on lazy-loading.
JavaScript: Eager, Defer, Async, and Lazy Hydration
JavaScript is the heaviest hitter on many sites, the real meat-and-potatoes of performance. Treat it like luggage on a small plane: only the essentials ride up front — seriously though, externalized and pipelined to a main.js is the expectation for a reason (if not using TypeScript).
-
Core behavior: Serve with
type="module"
(defer by default) ordefer
on classic scripts. Keep it tiny. - Third-party / non-critical code: load after paint, on idle, or on user intent (click/hover/intersection).
-
Split features: use dynamic
import()
to lazy-load code when a component approaches the viewport.
<script type="module">
// Lazy-init the gallery when it scrolled into view
const el = document.querySelector('[data-gallery]');
if (el && 'IntersectionObserver' in window) {
const io = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting) {
io.disconnect();
const { initGallery } = await import('/assets/js/gallery.js');
initGallery(el);
}
}, { rootMargin: '200px' });
io.observe(el);
}
</script>
Bonus: If a module is definitely needed on first view, give it a head start with
<link rel="modulepreload">
. If it’s not essential, keep it lazy to
protect INP (Interaction to Next Paint).
Common Anti-Patterns (AKA: How Sites Accidentally Get Slow)
- Lazy-loading the LCP image: Your hero should never wait.
- Omitting intrinsic sizes: No
width
/height
means CLS nightmares. - Preloading everything: Turns your runway into a parking lot, so preload only what paints first view.
- Carousels that lazy-load the “current” slide: Users see a spinner instead of content.
- Third-party widgets early: Treat them as guests. They don’t board before the crew.
- JS-only lazy logic: Native
loading="lazy"
is simpler; JS should enhance projects, not replace the basics.
Loading Decision Tree (Use This in Code Review)
- Will this asset be visible above the fold? Yes → eager. No → keep evaluating.
- Does it meaningfully affect first impression (LCP/First Paint)? Yes → eager + maybe
fetchpriority="high"
. No → lazy. - Can it be user-triggered? Yes → lazy import; maybe load on hover/click.
- Is it third-party? Yes → load after paint or on interaction; sandbox iframes; set
loading="lazy"
. - Have you declared width/height? If not, fix it before shipping.
Measure What Matters (and Don’t Chase Ghosts)
Use lab tools for direction (not as the law), then confirm observations in the real environment. In Chrome DevTools, record a Performance trace and open the “Largest Contentful Paint” overlay to verify your LCP element and its load chain. In Lighthouse or PageSpeed Insights, watch the “opportunities” tied to render-blocking resources. On repeat views, confirm that lazy-loaded assets don’t regress INP via main-thread blocks.
- LCP target: ≤ 2.5s on mobile.
- CLS target: ≤ 0.05 (sizes + aspect ratios).
- INP target: ≤ 200ms (defer long tasks, lazy-hydrate).
For implementation details on lazy images, see
web.dev.
For the loading
attribute’s behavior across elements, check
MDN.
And when you need an early boost for a critical asset, use
preload
plus priority hints.
SEO: Lazy-Loading Without Harming Your Visibility
Search engines are fine with native lazy loading. Where teams trip-up is with custom
JavaScript that withholds markup until after interaction, or swaps src
for data-src
without a noscript
fallback. If the HTML isn’t in
the DOM then crawlers may miss it — or, if you are lucky, rank it later.
- Prefer native
loading="lazy"
over bespoke JS. - Keep semantic HTML (real
<img>
,<h*
headings, lists). - For mission-critical imagery (product, article lead), don’t lazy-load at all.
- For complex client-side rendering, ensure server-rendered HTML exists.
Copy-Paste Checklist (Use on Every Page)
- Mark the intended LCP element. Ensure it’s not lazy-loaded.
- Add
fetchpriority="high"
to the LCP image; preload background heroes. - Declare
width
/height
(oraspect-ratio
) for all media. - Use
loading="lazy"
for below-the-fold images and every iframe. - Split JS: ship core with
type="module"
, lazy-import features on demand. - Audit third-party scripts. Load on interaction or after first paint.
- Measure LCP/CLS/INP in DevTools and field data. Iterate.
Bottom Line: Treat Priority Like a Product Decision
Lazy vs. eager isn’t about zealotry; it’s about sequence. Put what matters first on the runway, and make the rest queue up like pros. Your pages will feel snappy, stable, and trustworthy—and that’s what converts.
If you’ve ever stared at a waterfall chart wondering where your time went, this is your lever. Make one thing fast, then scale the discipline across your site. That’s how you stack wins: one prioritized byte at a time.