<head> Resources

Learn how to load external resources from <head> — stylesheets, favicons, preloaded and preconnected assets, alternate resources — and understand exactly how script loading timing differs between plain <script>, async, defer, and type="module".

Beyond metadata, <head> is also where you tell the browser which external files the page depends on: stylesheets, icons, fonts, and scripts. How and where you load these resources has a direct, measurable effect on page speed and on whether your JavaScript runs correctly — this is one of the most common sources of subtle bugs and one of the most frequently asked interview topics in web development.

Linking resources with <link>

<link rel="stylesheet" href="styles.css"> loads an external CSS file. Because CSS blocks rendering (the browser won't paint content until it knows how to style it), stylesheet links are conventionally placed in <head> so styling is ready before the page paints.

<link rel="icon" href="favicon.ico"> declares the favicon, as covered in the metadata lesson.

<link rel="preload" href="font.woff2" as="font" crossorigin> tells the browser to start fetching a critical resource immediately, at high priority, even before it's discovered in the normal parsing order — useful for a font or hero image the page needs right away, avoiding a flash of unstyled content.

<link rel="preconnect" href="https://fonts.gstatic.com"> tells the browser to start the DNS lookup, TCP handshake, and TLS negotiation for a third-party domain early, before any resource from it is actually requested — shaving real time off the eventual first request to that domain.

<link rel="alternate"> points to an alternate version of the page — an RSS feed (type="application/rss+xml"), a PDF version, or a translated version of the page (hreflang) for a different language/region.

Where <script> goes, and why it used to matter so much

By default, when the HTML parser hits a <script> tag, it stops parsing the page entirely, downloads the script (if external), executes it fully, and only then resumes parsing the rest of the HTML. This is why, historically, developers placed <script> tags at the very bottom of <body> — so the page's visible content would parse and render first, before a (possibly slow) script blocked everything.

Modern HTML gives us two attributes, async and defer, that change this blocking behavior for external scripts loaded from <head>, letting us keep scripts in <head> without sacrificing page-load speed.

Plain, async, and defer — a timing comparison

Plain <script src="a.js"></script>: HTML parsing stops, the script downloads, then executes immediately, then parsing resumes. Scripts run in source order exactly where they appear.

<script async src="a.js"></script>: the script downloads in the background while HTML parsing continues, but the moment it finishes downloading, parsing pauses and the script executes immediately — whenever that happens to be. Multiple async scripts can execute out of order, whichever finishes downloading first.

<script defer src="a.js"></script>: the script also downloads in the background without blocking parsing, but execution is deliberately delayed until the entire HTML document has finished parsing (just before the DOMContentLoaded event). Multiple defer scripts always execute in their original source order.

Rule of thumb: use defer for most application scripts that need the DOM to exist and need to run in a predictable order (this is why it's the modern default recommendation, even for scripts placed in <head>). Use async for independent, order-agnostic scripts like analytics trackers that don't touch the DOM and don't depend on anything else.

ES modules with type="module"

<script type="module" src="app.js"> loads the script as a native JavaScript ES module, enabling import/export syntax between files. Module scripts are deferred by default automatically (they don't block parsing and execute after parsing completes), run in strict mode automatically, and each module has its own top-level scope so variables don't leak into the global namespace the way plain scripts do.

Resource loading patterns

<head>
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="icon" href="/favicon.ico">
  <link rel="alternate" type="application/rss+xml" href="/feed.xml" title="Site Feed">

  <script src="analytics.js" async></script>
  <script src="app.js" defer></script>
  <script type="module" src="main.mjs"></script>
</head>

Code Examples

This throws an error: at the moment app.js runs, the parser has only processed <head> — the <button> doesn't exist in the DOM yet, so getElementById returns null and calling .addEventListener on it crashes.

Common mistake: Fixing this by moving the whole <script> tag to the bottom of <body> works, but the modern fix is to add the defer attribute and leave it in <head> — defer guarantees the DOM is fully parsed before the script runs, without giving up the organizational benefit of keeping scripts in <head>.

This is the key interview-level distinction: async is fire-as-soon-as-ready (order not guaranteed), while defer is guaranteed source order, always after parsing completes.

<head>
  <script src="app.js"></script>
</head>
<body>
  <button id="save-btn">Save</button>
</body>

<!-- app.js -->
<!-- document.getElementById('save-btn').addEventListener('click', ...) -->
<script src="slow-3s.js" async></script>
<script src="fast-1s.js" async></script>
<!-- With async: fast-1s.js likely logs BEFORE slow-3s.js,
     because it finishes downloading first, regardless of source order. -->

<script src="slow-3s.js" defer></script>
<script src="fast-1s.js" defer></script>
<!-- With defer: slow-3s.js ALWAYS logs before fast-1s.js,
     because defer preserves source order no matter which download finishes first. -->

Script loading timelines

Plain scripts block immediately. async blocks only once the download finishes (order not guaranteed). defer never blocks parsing and always preserves source order, running right before DOMContentLoaded.

plain:   [--parse-HTML--][download+run script][--parse-HTML--]
async:   [--parse-HTML continues--]
              [--download--][run!]---(pauses parsing right here)
defer:   [--parse-HTML continues to the end--][run in source order]
module:  behaves like defer by default

Important Rules

Stylesheets belong in <head> so the browser knows how to style content before it paints.

Prefer defer over placing scripts at the bottom of <body> for most application code — it keeps markup organized in <head> without blocking rendering.

Use async only for independent scripts that don't rely on the DOM or on execution order (e.g. analytics).

type="module" scripts are deferred by default — you don't need to add defer alongside it.

preconnect is for domains you'll definitely request from soon; overusing it wastes the browser's limited early-connection budget.

In practice

Modern build tools (Vite, webpack) automatically inject type="module" or defer scripts for you, but understanding what's actually happening under the hood matters the moment something breaks — like a third-party widget script that must load async because it's unrelated to your app, sitting next to your own defer-loaded application bundle that must run in order.

<script async src="app.js"></script> <script async src="app-helpers.js"></script>

<script defer src="app.js"></script> <script defer src="app-helpers.js"></script>

If app.js depends on functions defined in app-helpers.js, async offers no order guarantee and the app can crash intermittently depending on network timing. defer guarantees app-helpers.js runs first if it's listed first.

<link rel="preload" href="everything.css" as="style"> <link rel="preload" href="hero.jpg" as="image"> <link rel="preload" href="video.mp4" as="video"> <!-- ...preloading 15 different assets -->

<link rel="preload" href="hero.jpg" as="image"> preload tells the browser 'fetch this immediately at high priority' — overusing it on many assets defeats its purpose by competing for the same bandwidth and can actually slow down the truly critical resource.

[Predict the Output] Two scripts are declared in this order in <head>: <script defer src="utils.js"></script> followed by <script defer src="main.js"></script>, where main.js calls a function defined in utils.js. utils.js is a large 500KB file and main.js is a tiny 2KB file. Which script's code executes first? <script defer src="utils.js"></script> <script defer src="main.js"></script> A. main.js executes first because it downloads faster, regardless of source order. B. utils.js executes first because defer guarantees scripts run in their original source order, regardless of individual download speed. C. Both execute at the same time, in parallel. D. Neither runs — defer scripts require type="module" to execute.

B. utils.js executes first because defer guarantees scripts run in their original source order, regardless of individual download speed. — Unlike async, defer preserves source order for execution no matter which file finishes downloading first. Since utils.js appears first in the markup, it always runs before main.js — which is exactly why defer, not async, is safe when one script depends on another.

Given a page with a stylesheet, a Google Fonts font from an external domain, an analytics script, and your own app.js which manipulates the DOM, write the full <head> resource block using the correct link and script attributes for each.

See the 'Resource loading patterns' syntax block above — it demonstrates exactly this combination.

Font domain → preconnect. Own app logic that touches the DOM → defer. Independent third-party tracker → async.