Resource Loading and Performance
Controlling when scripts, styles, and images load with async, defer, and resource hints.
3 min read
A page's markup decides not just what loads, but when — and getting that order wrong is one of the most common causes of a slow-feeling page, independent of how fast the network or server actually is.
Why script placement matters
<head>
<script src="analytics.js"></script>
</head>
<body>
<h1>Welcome</h1>
</body>By default, a <script> tag blocks HTML parsing the instant the browser reaches it — it stops, downloads the file, runs it, and only then continues parsing the rest of the page. A blocking script in <head> delays everything below it from even appearing, which is why "put your scripts at the bottom of <body>" became a long-standing rule of thumb before better tools existed.
async and defer
<script src="analytics.js" async></script>
<script src="app.js" defer></script>Both attributes let the browser keep parsing HTML while the script downloads in the background, but they differ in when the script actually runs:
async— runs the script the instant it finishes downloading, wherever the parser happens to be. Scripts can run out of order relative to each other. Good for independent, self-contained scripts like analytics that don't touch the DOM or depend on anything else.defer— waits until the HTML document is fully parsed before running, and multipledeferscripts always run in the order they appear in the markup. Good for scripts that need the full DOM to exist, or that depend on each other.
Neither attribute does anything for an inline <script> block (no src) — they only affect externally loaded files.
Resource hints: preload, prefetch, preconnect
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preconnect" href="https://api.example.com" />
<link rel="prefetch" href="/next-page-bundle.js" />preload— tells the browser "download this now, you'll need it soon," for a resource the browser wouldn't otherwise discover early (like a font referenced only inside a CSS file). It raises the resource's priority for the current page.preconnect— starts the DNS lookup, TLS handshake, and TCP connection to another origin ahead of time, so the first real request to it doesn't pay that setup cost. Worth it for a small number of critical third-party origins (an API, a font CDN) — preconnecting to everything wastes the browser's limited connection budget.prefetch— a lower-priority hint for a resource likely needed on the next navigation, not this one (like the JS bundle for a page the user is probably about to visit).
Lazy-loading below-the-fold content
<img src="chart.png" alt="Quarterly revenue chart" loading="lazy" width="600" height="300" />
<iframe src="https://example.com/embed" loading="lazy" title="Embedded content"></iframe>loading="lazy" defers loading an image or iframe until it's about to scroll into the viewport, saving bandwidth and speeding up initial page load for content the user may never scroll to. Never lazy-load an image that appears immediately on load (like a hero image) — doing so actually delays the page's most important visual content, which is the opposite of the intent.
Why this is a "structure" problem, not just a script problem
None of these are JavaScript optimizations — they're decisions encoded directly in HTML about the order and priority of network requests. A page can run perfectly efficient JavaScript and still feel slow if its markup forces the browser to fetch a large blocking script before it can render anything, which is why resource loading strategy belongs in the same conversation as semantic markup and accessibility: all three are about writing HTML that works with how browsers actually process a page, not against it.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.