Why this exists

"Use async for order-independent stuff, use defer for anything that needs the DOM or a specific order" is a correct rule almost everywhere. Applying it mechanically to every <script> tag on a real site is a different exercise, because a handful of scripts don't announce which category they're in just from what they do.

This is that audit, across every real page on this site - what got reclassified, what stayed exactly as it was, and the one script where the obvious-looking answer was wrong.

The starting point

Real content pages, each loading the same set of scripts: header-min.js and favicon-min.js in the head, then jquery.min.js, two jQuery plugins, browser.min.js, breakpoints.min.js, main.js, and footer-min.js before the closing tag. The second group was already consistently marked defer on every page. The first group wasn't - header-min.js and favicon-min.js had no loading attribute at all on most pages, and defer on a handful of others, with no pattern explaining the difference.

What the audit found

  • favicon-min.js only appends <link rel="icon"> tags to the head - zero rendering impact, zero dependency on the DOM body, zero dependency on any other script. A clean async candidate.
  • header-min.js injects preconnect hints and the Font Awesome / Google Fonts stylesheet links - not a tracker, but still order-independent relative to every other script on the page, and with no DOM-readiness requirement of its own.
  • jquery.min.js through footer-min.js form a real dependency chain: the jQuery plugins need jQuery defined first, main.js reads globals that browser.js and breakpoints.js set up, and footer-min.js registers the <scott-footer> custom element that's already sitting in every page's HTML by the time these scripts run.
  • The inline Google tag (gtag.js) snippet has no src attribute at all, so async and defer don't apply to it either way - it already handles its own asynchronous loading internally by setting .async = true on the gtag/js element it creates at runtime.

The decision log

Decision 1: favicon-min.js gets async, no second-guessing

This one wasn't close. Favicon links don't affect layout, paint, or any other script's behavior. There's no scenario where this script running slightly later or slightly out of order changes anything a visitor sees. async, across all pages.

Decision 2: header-min.js gets async, not defer

This is the one that looked like it should be defer at first glance - it touches the head, and "touches the head" reads a lot like "needs to be careful about timing." But defer specifically means "wait until the entire document has finished parsing," and this script injects the Font Awesome and Google Fonts stylesheet links. Pushing that injection until after the whole page has parsed is the exact regression documented in a previous audit on this site - it dropped the mobile PageSpeed score from 71 to 51 the last time a font-related script got delayed like that. async keeps the parser unblocked without pushing the injection all the way to the end of the document.

Decision 3: leave the jQuery-to-footer chain exactly as it was

These six scripts were already correctly marked defer on every page, and that was worth confirming rather than assuming. defer scripts execute in the order they appear in the document, right before DOMContentLoaded - which is precisely what this chain needs, since each script after the first one depends on something the previous one set up. Marking any of these async would let the browser run them in whatever order they finish downloading, which for a same-origin set of small files could easily mean main.js running before browser.js has set the values it reads.

Decision 4: don't add attributes to the inline gtag.js snippet

The gtag.js bootstrap script has no src, and per the HTML spec, async and defer have no effect on a classic script without one - browsers ignore them on inline scripts. Adding either would be a no-op at best, and worth documenting as a no-op rather than leaving it to look like an oversight.

Before and after

Script Before After
header-min.js No attribute on most pages, defer on a few async, consistent across all pages
favicon-min.js No attribute on most pages, defer on a few async, consistent across all pages
jquery.min.js through footer-min.js defer, in document order Unchanged - already correct
Inline gtag.js bootstrap No src, no attribute Unchanged - attributes don't apply

What this actually took

A regex across all HTML files, two of which turned out to be directory placeholders with no scripts to touch at all. The actual work was smaller than the checking: confirming every <script src> tag site-wide ended up with exactly one loading attribute, that no tag picked up a duplicate, and that every embedded JSON-LD block still parsed after the sweep. None of that shows up in a diff - it's the difference between a change that looks done and one that's actually verified. Auditing script-loading behavior like this falls under Web & Digital work, the same practice applied here to this site's own script tags.

Frequently asked questions

Why not just mark every script async and let the browser sort it out?

Because "sort it out" is exactly what async doesn't guarantee - it explicitly gives up execution order between scripts. For scripts with real dependencies, like a plugin that needs its library loaded first, that's not a performance optimization, it's a race condition.

How do you tell if a script actually needs defer instead of async?

Ask two questions: does it read or rely on something another script sets up, and does it need the page's HTML to be fully parsed before it runs. A yes to either points to defer. A no to both, and no rendering impact either way, points to async.

Does the order of async scripts in the HTML matter at all?

Not for execution order between them - that's determined by whichever one finishes downloading first, not by document position. Position still matters for readability and for where the browser discovers the tag during parsing, just not for the order the scripts actually run in.

Not sure which of your own scripts are actually safe to make async?

A short conversation is usually enough to map out which scripts have real dependencies and which ones don't.