~/TechPurAI
~/tutorials/html-from-scratch/html-performance-basics
intermediate·part 19 of 22·4 min read

HTML performance basics: lazy loading, preload, and script loading

Updated Aug 16, 2026HTML

Performance is largely treated as a CSS and JavaScript concern, but HTML itself has real, direct control over resource loading order and priority — the attributes covered in this part, tying directly back to the Core Web Vitals concerns the SEO series covers from a strategy angle.

loading="lazy": deferring what isn't immediately visible

html
<img src="/images/roasting-step-3.jpg" alt="..." loading="lazy" />
<iframe src="https://www.youtube.com/embed/..." title="..." loading="lazy"></iframe>

Covered in parts 6 and 13 individually — worth restating together here as the same real principle applied to two different element types: don't load a real resource until it's genuinely about to be needed. The one real exception, covered next, is content visible the instant the page loads.

Never lazy-load what's immediately visible

html
<!-- wrong: this image is the first thing a visitor sees — lazy-loading
     it only delays what should load fastest -->
<img src="/images/hero-coffee-pour.jpg" alt="..." loading="lazy" />

<!-- correct: eager (the default) for above-the-fold content -->
<img src="/images/hero-coffee-pour.jpg" alt="..." loading="eager" />

loading="lazy" on an image that's visible the moment the page loads (a hero image, a logo) actively delays it — the browser has to first determine the image is "about to scroll into view," which for an already-visible image is immediate but still adds a real, measurable delay compared to loading it eagerly from the start. This directly affects Largest Contentful Paint, a real Core Web Vitals metric — lazy-loading the single largest above-the-fold image is a genuinely common, real mistake that actively hurts the exact metric someone was likely trying to improve.

html
<head>
  <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin />
  <link rel="preload" href="/images/hero-coffee-pour.jpg" as="image" />
</head>

A browser discovers most resources by parsing the HTML top to bottom and encountering references to them along the way — a background image referenced only inside a CSS file, for instance, isn't discovered until that CSS file itself has been fetched and parsed, a real, measurable delay. preload tells the browser about a genuinely critical resource immediately, from the <head>, letting it start fetching in parallel rather than waiting to discover it later in the normal loading sequence — worth reserving specifically for resources that are both critical and not otherwise discovered early, like a custom font or a real hero image, not applied broadly to every resource on the page.

async and defer: the real difference for scripts

html
<script src="/js/analytics.js" async></script>
<script src="/js/main.js" defer></script>
text
No attribute: the browser stops parsing the rest of the HTML entirely
  until the script downloads and finishes executing — a real,
  significant blocking delay
async: downloads in parallel with HTML parsing, then pauses parsing
  to execute the instant it's ready — execution order relative to
  other async scripts isn't guaranteed
defer: downloads in parallel with HTML parsing too, but execution is
  held until parsing finishes, in the real, original document order

A real, practical rule of thumb: async for scripts that don't depend on the page's DOM or on other scripts (an analytics snippet, similar in spirit to this site's own deferred GTM loading approach); defer for scripts that need the full DOM to exist first, or that need to run in a specific real order relative to other scripts — which covers most genuine application logic.

Why it matters

A <script> tag with neither async nor defer, placed in <head>, is one of the most common real causes of a slow-feeling page — the browser stops doing anything else, including rendering visible content, until that script finishes. This is exactly why a script tag with no loading attribute has traditionally been placed at the very end of <body> instead of in <head> — a real, older workaround for a problem async/defer now solve more precisely.

Resource hints: preconnect and dns-prefetch

html
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />

For real, known third-party origins a page will definitely request something from (a font host, an analytics domain), preconnect lets the browser establish the real DNS lookup, TCP connection, and TLS handshake ahead of time, in parallel with everything else — so the actual resource request, when it happens, skips straight to the data transfer instead of paying that full connection setup cost at that moment. dns-prefetch is a lighter, more limited version doing just the DNS lookup — worth using as a fallback for older browsers that don't support preconnect.

Next: common HTML mistakes — a direct, honest roundup of the real failure patterns this series has flagged individually, brought together in one place.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous18. Character encoding and HTML entitiesnext →20. Common HTML mistakes that quietly hurt SEO and accessibility