async vs defer on 27 homepages: 625 external scripts, and 73 still block the parser
The async vs defer choice has a third answer nobody picks on purpose. Across 27 homepages, 73 of 625 external scripts carry neither attribute and stop the parser; 22 of them sit in the head, and four sites account for 55 of the 73.

FIELD TEST · 2026-08-26 · 27 homepages · single crawl · raw HTML only
Sample and method: the same 30-site panel in use since 2026-08-15, each homepage fetched once on 2026-08-26 with a browser user agent, no rendering. We read every <script> element in the raw HTML and recorded its src, its type, and whether the async and defer attributes were present.
The async vs defer question has a third answer that nobody picks on purpose: neither. Across 27 homepages we counted 1,356 script elements, 625 of them external. Of those, 187 carry async, 362 carry defer, 150 are modules — and 73 carry nothing, which means they stop the parser until they have been fetched and run. Twenty-two of the 73 sit in the head.
How we measured it
One HTTP request per site, no rendering, no retries. Inclusion was fixed before the run: final status 200, decoded body at least 10,000 bytes, body containing <body. Three of the 30 failed — stackoverflow.com and medium.com returned 403, www.reddit.com returned an 8,393-byte shell — leaving 27, the same three exclusions as previous batches.
Both attributes were read as boolean by presence, never by value. That rule comes from a mistake in an earlier batch, where reading a boolean attribute by value produced a headline that was false. A script counts as parser-blocking here only if it has a src, has neither attribute, and is a classic script — modules defer by default, so counting them would inflate the number.
# Your own count, same definition
curl -sL --compressed -A 'Mozilla/5.0' https://example.com/ \
| grep -o '<script[^>]*src=[^>]*>' \
| grep -v -E 'async|defer|type="module"' | wc -l
What we did not measure: whether any of these scripts actually delayed anything for a real visitor. Parser-blocking is a property of the markup, and the wall-clock cost depends on connection, cache and file size, none of which we captured.
async vs defer: what each one actually changes
Both attributes remove the block. They differ in when the script runs and whether order is kept, which is the part that decides which one you want.
| Markup | Parser | Runs when | Order kept |
|---|---|---|---|
plain src | Stops | Immediately | Yes |
async | Continues | As soon as fetched | No |
defer | Continues | After parsing | Yes |
type="module" | Continues | After parsing | Yes |
MDN's script element reference, read on 2026-08-26, states the default plainly: "if a script element does not include type="module", async, or defer, then it blocks parsing, not rendering." The same page notes that "only script elements in the document's <head> can possibly block rendering", which is why we counted head placement separately.
Seventy-three blocking scripts, and four sites own 55 of them
The count is not spread evenly. Fourteen of 27 homepages have zero parser-blocking external scripts. Thirteen have at least one, and four of those thirteen account for 55 of the 73.
| Site | External | Blocking | In head |
|---|---|---|---|
slack.com | 16 | 16 | 4 |
webflow.com | 18 | 14 | 2 |
techcrunch.com | 20 | 13 | 5 |
discord.com | 18 | 12 | 3 |
arstechnica.com | 8 | 6 | 1 |
| 8 other sites | — | 1 to 3 each | 0 to 2 |
Slack is the clean case: all 16 of its external scripts are plain. The ones in its head include a consent-manager stub and a marketing bundle, so the parser stops for a third-party fetch before it reaches the page's own content. The pattern repeats on the other three — a tag manager, a jQuery copy, a consent script.
The sites with a build step behave differently. nextjs.org, vercel.com, figma.com and supabase.com have exactly one blocking script each, and in every case it is something the framework emitted to run before everything else: a polyfill bundle on two of them, the same shared chunk filename on the other two. One deliberate exception is a different thing from sixteen accidental ones.
Two attributes on one tag, on four sites
Seventeen scripts carry both async and defer: eight on theverge.com, five on bbc.com, three on wired.com, one on netlify.com. This looks like a mistake and is not one. MDN: "If the attribute is specified with the defer attribute, the element will act as if only the async attribute is specified."
It is the documented fallback for browsers old enough to understand defer and not async. Most of the 17 are third-party tag snippets — a consent manager, an ad exchange tag, a Google publisher tag — which is where you would expect defensive markup written years ago to survive. Three of the BBC's five are its own bundles, so the habit spreads inward once it is in a template.
Ten attributes that do nothing at all
Nine inline scripts in the sample carry async, and one carries defer. Both are no-ops. MDN, on each attribute: "This attribute must not be used if the src attribute is absent (i.e., for inline scripts), in this case it would have no effect."
Ten out of 731 inline scripts is not a crisis. It is a small, reliable sign of copy-paste, and worth a grep on your own templates.
A script tag with no attribute is a decision. Most of the time nobody made it.
The spread, and the site with no external scripts
External script counts run from 0 to 89, median 18. substack.com is highest at 89, all deferred and 88 of them modules. At the other end, shopify.com ships 8 script elements and not one of them has a src — everything is inline, so there is nothing to block on and nothing to fetch.
Those two are the same answer arrived at from opposite directions. Neither of them has a parser-blocking script, and neither of them got there by adding attributes one at a time.
What this means for a crawler
Google renders with a headless browser, so parser-blocking scripts cost it time rather than content. Google's crawl budget guidance, read on 2026-08-26, ties the two together: "Make your pages efficient to load. If Google can load and render your pages faster, we might be able to read more content from your site."
Retrieval clients that do not render are a different case. They read the HTML that arrives and stop, so a blocking script costs them nothing and gains them nothing — what matters there is whether your text was in the document at all, which is the subject of whether AI can crawl JavaScript.
- Add
deferto anything that touches the page, and keep the order - Add
asyncto anything that is independent of everything else, such as an analytics tag - Do not paste a third-party snippet without reading its script tag. That is where 55 of the 73 came from
- Do not put either attribute on an inline script. It does nothing, and it suggests the tag was copied
The head is where these decisions accumulate. We have measured two of its other residents on the same panel — 650 resource hints across 27 homepages and loading lazy versus eager on 1,677 images — and the shape is the same each time: a handful of sites are deliberate, most inherited what a snippet handed them. If you want to see how a page reaches an engine before any of this runs, see how QueryWin works.
Common questions
How did you measure this?
One request per homepage on 2026-08-26 with a browser user agent, bodies written to disk, script elements read by an HTML parser with both attributes treated as boolean-by-presence, and every published number recomputed from the saved files.
In async vs defer, which one is better?
Neither, and the choice is not about speed. Use defer when the script needs the document or needs to run after another script; use async when it needs neither. Both let the parser keep going, which is the part that matters.
What about scripts at the end of the body?
They still block the parser at the point they appear, which by then is after your content. It works, and defer in the head gives you the same execution timing plus an earlier fetch. We counted head placement separately for exactly this reason.
Do these attributes affect what Google indexes?
Not directly. Google queues pages with a 200 status for rendering, so the script runs either way. The effect is on how fast the page can be loaded and rendered, which Google's own crawl budget documentation links to how much of a site gets read.
Should modules get defer too?
No. MDN is explicit: "The defer attribute has no effect on module scripts — they defer by default." The 150 modules in this sample are already off the blocking path.


