Performance
Third-party JavaScript: what it really costs
A synchronous third-party script in the head moved first paint by 388 ms in the lab: 296 ms without it, 684 ms with lodash, 284 ms deferred. Here is the cost.
Key points
- The cost of a third-party script is when you load it, not only how large it is.
- Synchronous in the head: FCP 684 ms against 296 ms without it, a 388 ms difference.
- The same file deferred leaves FCP at 284 ms, with the same bytes on the wire.
- A third-party host pays for an extra handshake: 63 ms of TLS before the first byte in our run.
A synchronous third-party script in the head moved first paint by 388 ms: in the run without scripts FCP is 296 ms, with lodash.min.js in the head it is 684 ms. The same file with defer leaves FCP at 284 ms, so the cost is not the file, it is when you load it.
Three variants, one file
| Variant | Requests | Bytes | FCP | Load |
|---|---|---|---|---|
| No script | 1 | 6,336 B | 296 ms | 231 ms |
| Synchronous in the head | 2 | 30,115 B | 684 ms | 667 ms |
| defer | 2 | 33,474 B | 284 ms | 601 ms |
The gap between synchronous and deferred is 400 ms of FCP: with the synchronous script the parser stops before it paints. The bytes of the two variants differ because of how the server compressed the response, not because of the content.
The cost you do not see
A third-party host pays for a handshake that the connection already open to your origin cannot reuse: in our run 63 ms of TLS and 86 ms to the first byte, before a single useful byte arrives. Then come the bytes of the file: 26,751 for lodash, 153,653 for a demo Google Analytics tag, read with curl the same day.
for u in https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js \
"https://www.googletagmanager.com/gtag/js?id=G-DEMO123"; do
curl -s -o /dev/null -H "Accept-Encoding: gzip, br" \
-w "$u dns=%{time_namelookup} tls=%{time_appconnect} ttfb=%{time_starttransfer} bytes=%{size_download}\n" "$u"
done
The command prints one line per host: DNS, end of TLS, first byte and bytes transferred. The number that matters is tls, because that is what your page pays before it can even ask for the file.
How to cut it
- Put everything the first paint does not need in
deferorasync: 400 ms of FCP in our run. Source: MDN, CC BY-SA 2.5. - Host the file on your own origin when you can: that removes the handshake to a third-party host and a DNS you do not control. Source: web.dev, CC BY 4.0.
- Add
rel=preconnectonly for hosts you really use, not for every tag someone opened in a meeting. Source: MDN, CC BY-SA 2.5. - Measure per tag: if nobody knows what a script does, the answer is to remove it. The demo tag weighs 153,653 bytes.
The limitation to name
The test covers one file, one page and one simulated network: a site with six tags does not pay the same delay six times, because connections are reused and scripts overlap. defer removes the parser block but not the main-thread work, and a 61 ms long task still appeared in one of the five synchronous runs. Consent, privacy and cookie banners carry a cost this test does not measure.
Where to continue
The EneUI catalogue indexes 111,985 components, the hero horizon-hero-section and the icon icon-access-point: a hero with video and an extra font weighs more than an analytics script. Read how to set a performance budget, bundle splitting and how much a UI library weighs.
Frequently asked questions
Are defer and async equivalent?
No. defer keeps the order between scripts and runs after parsing; async runs as soon as the file arrives and can jump ahead of a script it depended on.
What does an analytics tag cost?
From 26,751 bytes for a small library to 153,653 bytes for gtag, plus a handshake to a third-party host. Time, not weight, decides first paint.
Is preconnect always worth it?
It is worth it when you really use the host and the connection opens early enough. On hosts you rarely call it only adds a connection to close.
Self-host or public CDN?
Self-hosting wins on first byte and control, and loses on automatic updates. For a script that rarely changes, self-hosting with a long cache is the simpler choice.
How do I measure the cost in the field?
Resource Timing gives the bytes and long tasks show the main-thread work. The lab tells you when you load it; field data tells you what it costs your users.