completefrontend
Open lab My progress
THE WEB PERFORMANCE FIELD GUIDE

The cost of third parties

Find every third party on a page, measure what each one costs in bytes, main-thread time and risk, and see what happens when one of their servers is down.

By the end, you can
  • 01
    Name the kinds of third partiesTell analytics, ads, tag managers and widgets apart, and find the ones a tag manager adds behind your back.
  • 02
    Measure each one's costSplit bytes and main-thread time by site in DevTools, then block one third party and compare.
  • 03
    Spot the riskUse the Third Party Web dataset before you add a tag, and test what your page does when a third party never answers.

Twelve tags on the tea of the week

Meera looks after Leaf and Kettle's marketing. Over the past year, she has added tags to the shop's pages. A tag is a small piece of code from another company. One counts visits. One tells an ad network who came. Each took her a minute to add. She adds them through a tag manager, a script that puts tags on the page for her. It now holds twelve of them.

On Monday, Asha sends the shop a message. The home page is slow on her phone. Ravi opens the page and reads its head. Next to his own code, he finds scripts from four other companies. And one of them adds two more. So Ravi asks a simple question. Which of these costs Asha the most?

Each of those companies is a third party. Its code or files come from its own site, not the shop's. Leaf and Kettle is the first party: it owns the page and its address.

The course didn't load any real analytics, ad or chat service. They would track the course's browser, and they change from day to day. So it built six stand-ins. Each one lives on its own made-up site, like tags.example. Each keeps the phone busy for about as long as a well-known service of its kind does, on average. Their file sizes are the course's own choice. You'll meet the list those averages come from later on.

Then it loaded the page on "Asha's phone", the stand-in from lesson 22.1. That's Chrome 157 on a phone's screen, DevTools' Slow 4G network, and a CPU 4 times slower. It loaded the page with no third parties, and with all of them, three times each. Here is what Asha saw:

Asha's screen on the tea of the week page, in real frames, at four moments. Without the tags: at 677 ms, The shop's name, the words and the button. The mint tea photo is still loading. at 1,274 ms, The page: the shop's name, the mint tea photo, the words and the button. at 2,923 ms, The page: the shop's name, the mint tea photo, the words and the button. at 4,433 ms, The page: the shop's name, the mint tea photo, the words and the button. With the tags: at 677 ms, A white screen: nothing drawn yet. at 1,274 ms, A white screen: nothing drawn yet. at 2,923 ms, The page, with the consent banner across the bottom. at 4,433 ms, The page, with the consent banner across the bottom and the orange chat bubble over its corner.677 ms1,274 ms2,923 ms4,433 msWithoutthe tagsThe shop's name, the words and the button. The mint tea photo is still loading.The page: the shop's name, the mint tea photo, the words and the button.The page: the shop's name, the mint tea photo, the words and the button.The page: the shop's name, the mint tea photo, the words and the button.Withthe tagsA white screen: nothing drawn yet.A white screen: nothing drawn yet.The page, with the consent banner across the bottom.The page, with the consent banner across the bottom and the orange chat bubble over its corner.
Real frames from Asha's phone, the middle of three loads of each page. Without the tags, the words came first, then the photo. With them, the screen stayed white until the page came all at once, with the consent banner. The chat bubble came last.

In the top row, the words came first, then the photo. Both were on screen within 1.3 seconds. In the bottom row, the screen stayed white until 2.9 seconds. Then the whole page came at once, with a banner across the bottom asking about cookies. The chat bubble came at 4.4 seconds.

Here are the numbers behind those screens:

The same page, without and with the tagsPop out in the code editor (opens in a new tab)JavaScript
// Asha's phone: the median of three loads of each page.const withoutTags = { photo: 1273, blocking: 0, bytes: 13216 };const withTags = { photo: 2920, blocking: 1135, bytes: 346544 };const kb = (bytes) => (bytes / 1024).toFixed(1); console.log(`Photo on screen: ${withoutTags.photo} ms, then ${withTags.photo} ms`);console.log(`Blocking time: ${withoutTags.blocking} ms, then ${withTags.blocking} ms`);console.log(`Downloaded: ${kb(withoutTags.bytes)} KB, then ${kb(withTags.bytes)} KB`);
  • Line 1 is a comment. Each number is a median of three loads: the middle one, once they're sorted.
  • Line 2 holds the page without the tags. photo is when the photo appeared, in milliseconds (ms). It was the page's largest contentful paint. blocking is the page's blocking time. It adds up how long the phone was too busy to answer a tap, after the first paint. bytes is everything the page downloaded.
  • Line 3 holds the same page with the tags. Line 4 turns bytes into kilobytes (KB).
  • Lines 6 to 8 print Photo on screen: 1273 ms, then 2920 ms, Blocking time: 0 ms, then 1135 ms and Downloaded: 12.9 KB, then 338.4 KB.

The tags more than doubled Asha's wait for the photo. They added over a second of blocking time. And they made her phone download 26 times as many bytes. Why it matters: Asha can't see or use the shop until other companies' code is done with her phone.

Real-life analogyOne electricity bill, many plugs

Four friends share a flat and one electricity bill. This month, the bill doubled. Who did it? The bill can't say. So they put a small meter on each plug. Or they switch one thing off for a week, and see how the bill changes.

In real life: The bill
In web performance: The page's load: its bytes, its blocking time, its wait for the photo
In real life: The fridge, the heater and the kettle
In web performance: Each third party on the page
In real life: A small meter on each plug
In web performance: DevTools splitting the cost by site
In real life: Switching one thing off for a week
In web performance: Blocking one third party, and loading the page again
The main idea

Every third party costs three things: bytes to download, time on the main thread, and the risk that its server is slow or down. The page's total can't tell you which one costs the most. You find out by measuring each one. There are two ways. Split the cost by site, or take one away and compare.

The main thread is the one line of work where the browser runs scripts and draws the page. Only one thing happens on it at a time.

The system design lesson Third-party scripts and supply chain covers what this code can do to your page and your visitors' data. It also covers how to keep it safe. This lesson measures what it costs in speed. Lesson 19.2 already measured three embeds. Lesson 23.1 showed how a script tag decides when a file runs. Here, you'll answer four questions:

  • What kinds of third parties are there? Analytics, ads, tag managers and widgets.
  • How do you measure each one? Measuring each third party's cost.
  • How can you know before you add one? The Third Party Web dataset.
  • What if one of them is down? Single points of failure.

Analytics, ads, tag managers and widgets

Third parties are on almost every page. The Web Almanac studies millions of pages from the HTTP Archive. Its 2025 chapter on third parties found them on more than nine in ten pages. The median mobile page made 79 requests to them.

Most fall into a few kinds. Here are the kinds on Meera's page:

  • Analytics counts visits, and what visitors do. Meera's analytics tag sends a small report for each event, like "the tea of the week was seen".
  • Ads show ads, or tell an ad network who visited. Then it can show them ads on other sites. Meera's ad script does the second.
  • A tag manager is a script that loads other scripts. Meera picks tags in its dashboard, and it adds them to the page. Nobody has to change the shop's code.
  • Widgets put something on the page. Meera has a chat widget, with a bubble in the corner.
  • A consent banner asks Asha which cookies she allows. It runs early on every page.
  • An A/B test shows half the visitors one version of a page, and half another. Then Meera can see which one sells more.

Here is the top of the page, with each company's snippet pasted as its instructions said:

The home page, shortened: four other companies' tagsHTML
<head>  <style>.ab-hide { opacity: 0 !important; }</style>  <script>    document.documentElement.classList.add("ab-hide");    setTimeout(() => document.documentElement.classList.remove("ab-hide"), 4000);  </script>  <script src="https://abtest.example/experiment.js" async></script>  <script src="https://consent.example/banner.js"></script>  <script src="https://tags.example/container.js?id=LK-12" async></script>  <script src="shop.js" defer></script></head><!-- ...the photo, the words and the button... --><script src="https://chat.example/widget.js" async></script>
  • Lines 2 to 7 are the A/B test's snippet. Line 2 makes a class that hides the whole page. Line 4 puts it on the page. Line 5 is a safety net. It shows the page anyway after 4 seconds. Line 7 loads the test itself, experiment.js.
  • Line 8 loads the consent banner as a plain script: no async, no defer.
  • Line 9 loads the tag manager's container. That's its file with Meera's twelve tags in it.
  • Line 10 is Ravi's own script. Line 13, at the end of the body, loads the chat widget.

Lesson 23.1 showed what a plain script does. The browser stops reading the HTML until the script has arrived and run. Remember line 8. It comes back at the end of the lesson.

A script that adds scripts

The HTML names four third parties. But the page talked to six. The tag manager added the other two. Here is its container, shortened:

container.js: the tag manager runs Meera's tagsJavaScript
// container.js, shortened: 2 of its 12 tags.const TAGS = [  { name: "Analytics: base tag", kind: "script", when: "start", src: "https://stats.example/analytics.js" },  { name: "Ads: landing page", kind: "pixel", when: "start", src: "https://ads.example/pixel?tag=landing" },];const run = (tag) => {  busy(16); // the tag's own code  if (tag.kind === "script") {    const script = document.createElement("script");    script.src = tag.src;    document.head.append(script);  } else new Image().src = tag.src;};const fire = (when) => TAGS.filter((tag) => tag.when === when).forEach(run);
  • Lines 2 to 5 list the tags. Each has a name, a kind, a trigger and an address. The real container lists twelve.
  • Lines 6 to 13 run one tag. Line 7 is the tag's own work. busy(16) is how the course's stand-ins work: it keeps the main thread busy for 16 ms.
  • Lines 8 to 11 add a script tag to the page. Only now does Chrome ask for that file.
  • Line 12 sends a pixel. That's a request for a tiny image, made only to report something. The report rides in the address.
  • Line 14 runs every tag whose trigger has come. A start tag runs at once. A ready tag waits for DOMContentLoaded, and a load tag for the load event.

Here is who asked for what, and when Chrome asked:

Who asked for which file on the page with every tag, and when Chrome asked. The page's HTML named four third-party scripts, asked for at about 618 ms: experiment.js, banner.js, container.js and widget.js. container.js, the tag manager, added analytics.js at 3,019 ms, ads.js at 3,052 ms, and 10 pixels: 6 from 3,135 ms and 4 after the load event, from 5,770 ms. widget.js asked for settings.json at 3,539 ms. analytics.js sent 1 pixel at 4,240 ms, and ads.js sent 2 at 5,112 ms.the page0 msexperiment.js617 msbanner.js618 mscontainer.js618 mswidget.js618 msanalytics.js3,019 msads.js3,052 ms10 pixels6 now, 4 after loadsettings.json3,539 ms1 pixel4,240 ms2 pixels5,112 ms
Each arrow points from a file to the files it asked for. The times are when Chrome asked. The HTML named four other sites' scripts. The tag manager's container brought in two more sites, and most of the requests.

Chrome asked for the container at 618 ms. It arrived at 2,550 ms. Only then could it run, and add analytics.js and ads.js. Those two then sent pixels of their own. So one line of HTML brought in two more sites, and 16 requests in all.

This is normal. The Almanac found that "the majority of the third parties include at least another third party". Why it matters: the HTML doesn't show every third party. Look at the requests the page really makes.

Real-life analogyOne builder, many helpers

You hire one builder to fix the kitchen. On Monday, an electrician turns up. Then a plumber. You never called them. The builder did. And the bill has all their hours on it, not just the builder's.

In real life: The builder you hired
In web performance: The tag manager's container
In real life: The electrician and the plumber
In web performance: analytics.js and ads.js, added by the container
In real life: A note left at the door
In web performance: A pixel: one tiny report
In real life: One bill with everyone's hours
In web performance: The tag manager's real cost: its own work, and every tag it adds

Measuring each third party's cost

Back to Ravi's question. In the flat, there were two ways to find the costly plug: a meter on each one, or a switch. The same two ways work on a page. Start with the meters: split the cost by site.

Bytes and requests, by site

Each third party answers from its own site. So group the page's requests by the site they went to. In DevTools' Network panel, More filters › 3rd-party requests keeps only requests to other sites. Here is what each site sent on the page with every tag:

What each site sentPop out in the code editor (opens in a new tab)JavaScript
// What each site sent, on the page with every tag: bytes and requests.const sites = [  ["shop.example", 13376, 3],  ["abtest.example", 27417, 1],  ["consent.example", 30716, 1],  ["tags.example", 71276, 1],  ["stats.example", 45864, 7],  ["ads.example", 60953, 8],  ["chat.example", 96942, 2],];for (const [site, bytes, requests] of sites) {  const word = requests === 1 ? "request" : "requests";  console.log(`${site}: ${(bytes / 1024).toFixed(1)} KB in ${requests} ${word}`);}
  • Lines 3 to 9 hold one site per row: its name, the bytes it sent, and how many requests it answered.
  • Line 12 picks the word "request" or "requests".
  • Line 13 prints one line per site. It starts with shop.example: 13.1 KB in 3 requests and ends with chat.example: 94.7 KB in 2 requests.

The chat widget sent the most: 94.7 KB, nearly all of it one script. The tag manager's container came next, at 69.6 KB. The shop's own page, photo included, was 13.1 KB. So Asha's phone downloaded about 25 times more from other companies than from the shop. Why it matters: on a slow network, those bytes share the line with the shop's photo.

Main-thread time, by third party

Bytes are only part of the cost. Each script also runs on the main thread. DevTools can split that time three ways:

  • In the Performance panel, the 3rd parties insight lists each third party. It gives each one's transfer size and main thread time.
  • With nothing selected, the Summary tab shows a 1st / 3rd party table. The page's own site carries a 1st party badge.
  • The Bottom-up tab has a grouping menu. Pick Group by 3rd parties.

Lighthouse shows the same table, in its own 3rd parties insight. Both use DevTools' trace engine. It gives each bit of work to the script file it ran from. Then it gives each file to its third party. The course ran that engine on its trace of the page:

Main-thread time by site, as the 3rd parties insight gives it for the page with every tag: ads.example, 681 ms; consent.example, 567 ms; abtest.example, 468 ms; chat.example, 443 ms; tags.example, 292 ms; stats.example, 110 ms; shop.example, 10 ms.ads.example681 msconsent.example567 msabtest.example468 mschat.example443 mstags.example292 msstats.example110 msshop.example10 ms
The Performance panel's 3rd parties insight, worked out from the course's trace by DevTools' own code. Other companies' code ran for 2,561 ms in all. The shop's own code, at the bottom, ran for 10 ms.

The ad script ran the longest: 681 ms. The consent banner came next, at 567 ms. The shop's own code ran for just 10 ms. In all, other companies' code ran for 2,561 ms. So the chat widget sent the most bytes, but the ad script held the phone the longest. Why it matters: the biggest file isn't always the biggest cost.

Take one away, and compare

The meters split the cost. They don't tell you what would change without one. For that, use the switch. Block one third party, load the page again, and compare. In the Network panel, right-click a request and open Block requests. Then pick Block request URL or Block request domain. The Request conditions drawer lists what's blocked. Load the page three or more times each way, and compare the medians. One load can mislead you.

The course did this for each third party, three loads each. Read each row across: what was taken out, then how four numbers moved. Each is compared with the page with every tag.

What taking out each third party changed, on Asha's phone
Taken outPhoto on screenBlocking timeDownloadedLoad event
The A/B test939 ms sooner216 ms more26.8 KB less786 ms sooner
The consent banner711 ms sooner10 ms more30.0 KB less840 ms sooner
The tag manager, and every tag in it204 ms sooner791 ms less173.9 KB less2,448 ms sooner
The six analytics tags139 ms later209 ms less64.7 KB less706 ms sooner
The six ad tags140 ms later770 ms less79.4 KB less1,219 ms sooner
The chat widget18 ms sooner346 ms less94.7 KB less213 ms sooner

Three findings stand out:

  • The tag manager cost the most blocking time. Without it, blocking time fell by 791 ms. The page downloaded 173.9 KB less. Yet its own code ran for only 292 ms. The rest came from the tags it added. So count what a tag manager adds as part of its cost.
  • The A/B test cost the most time to the photo. Without it, the photo came 939 ms sooner. Yet its own code ran for 468 ms, less than the ad script or the banner. Its snippet hid the page until that code had run. No main-thread table shows a hidden page.
  • The consent banner held the photo back too, by 711 ms. Its plain script stopped the browser reading the HTML.
Real-life analogyBlinds down until the window is dressed

A shop keeps its blinds down until the window dresser has finished. She only needs ten minutes. But she's stuck in traffic for an hour first. The shop stays dark the whole time, though everything inside is ready.

In real life: The blinds
In web performance: The A/B snippet's class, which hides the page
In real life: The window dresser's ten minutes of work
In web performance: experiment.js's own main-thread time
In real life: Her hour in traffic
In web performance: experiment.js downloading, then waiting for DOMContentLoaded
In real life: Customers outside a dark shop
In web performance: Asha, looking at a white screen

Two surprises

First, look at the blocking time without the A/B test. It went up, by 216 ms. Blocking time only counts long tasks after the first paint. A long task holds the main thread for more than 50 ms. With the page hidden, the first paint came late. So the early long tasks weren't counted, though they still kept Asha's phone busy. Why it matters: a lower blocking time isn't always a better page. Check when the first paint came.

Second, without the ad tags or the analytics tags, the photo came about 140 ms later. The smaller container arrived sooner: at 2,415 ms instead of 2,550. That was just before the browser had read all the HTML. So the container's 161 ms long task ran first, and the page waited behind it. Why it matters: taking one script away can move another's work in front of your page. Measure. Don't guess.

Watch each page fill in. Move the slider:

Playground: eight pages, moment by moment
The same page, with different third parties
  1. No third partiesThe page: the shop's name, the mint tea photo, the words and the button.the page
  2. Every third partyA white screen: nothing drawn yet.a white screen
  3. Without the A/B testA white screen: nothing drawn yet.a white screen
  4. Without the consent bannerA white screen: nothing drawn yet.a white screen
  5. Without the tag managernothing drawn yet
  6. Without the analytics tagsnothing drawn yet
  7. Without the ad tagsA white screen: nothing drawn yet.a white screen
  8. Without the chat widgetA white screen: nothing drawn yet.a white screen
Try it yourself
Asha's screen at 1,300 ms

Move the slider. Each frame is the last one Chrome had drawn by then, on the middle of three loads. Each page leaves out one third party, or none, or all.

Chrome 157 on a phone's screen, DevTools' Slow 4G, a CPU 4 times slower.

Now block one third party at a time, and watch the numbers move:

Playground: block one third party
Asha's phone
  1. Photo on screen (LCP)2,920 ms
  2. Blocking time1,135 ms
  3. Load event5,704 ms
  4. Downloaded338.4 KB
  5. Requests23
Try it yourself
Meera's page, with every tag

Each number is the median of three loads. In brackets: how far it moved from the page with every tag.

Chrome 157 on a phone's screen, DevTools' Slow 4G, a CPU 4 times slower.

The Third Party Web dataset

Measuring needs the tag on your page. But Meera wants to know before she adds the next one. For that, there's a public list.

Third Party Web is an open dataset by Patrick Hulce. Each month, the HTTP Archive loads millions of sites with Lighthouse, as a phone. Lighthouse adds up how long each script ran on the main thread. Third Party Web adds those times up for each origin: the scheme, host and port a script came from. Then it gives each origin to the company behind it. That company is an entity: one name, like Google Analytics, for all the origins it serves from.

For each entity, it gives an Average Impact. That's the main-thread time of all its scripts, divided by the number of pages that use it. So it says how long that service runs on a typical page. The README sorts the entities into categories, from the lightest to the heaviest. For example, the most used tag manager, Google Tag Manager, averages 1,066 ms.

Real-life analogyAn energy label on a new fridge

Before you buy a fridge, you read its energy label. It says how much power that model uses in a year, on average. Your kitchen may be different. But you can compare two fridges before you plug either one in.

In real life: The energy label
In web performance: An entity's row in Third Party Web
In real life: Power used in a year, on average
In web performance: Average Impact: main-thread time per page that uses it
In real life: Comparing two fridges in the shop
In web performance: Comparing two chat services before you add one
In real life: Your own kitchen
In web performance: Your own page, where you still measure

The course sized its stand-ins from this dataset. Each keeps the main thread busy for about the Average Impact of one well-known service of its kind. Read each row across: the stand-in, its kind, the dataset row it copies, that row's Average Impact, and what DevTools measured on Asha's phone.

The stand-ins, and the dataset rows they copy
The stand-inIts kindSized afterAverage Impact in the datasetMain-thread time on Asha's phone
abtest.exampleA/B testingVWO (Analytics)465 ms468 ms
consent.exampleConsent bannerOptanon (Consent Management Provider)564 ms567 ms
tags.exampleTag managerTealium (Tag Management)283 ms292 ms
stats.exampleAnalyticsGoogle Analytics (Analytics)108 ms110 ms
ads.exampleAdvertisingAmazon Ads (Advertising)679 ms681 ms
chat.exampleChatTawk.to (Customer Success)440 ms443 ms

The last two columns match within 10 ms, because each stand-in works for a fixed time. Real services vary from page to page. That's why the dataset gives an average.

The same list names the third parties in your tools. DevTools' docs say its 3rd parties insight groups requests "based on the Third Party Web open source dataset". Lighthouse uses the same package. A site the list doesn't know is named after its domain. That's why the course's stand-ins show up as ads.example and chat.example.

Keep two limits in mind. The dataset only counts main-thread time. It knows nothing about the A/B test's hidden page, or a plain script that stops the HTML. And its numbers come from other people's pages, not yours. Use it to compare before you choose. Then measure on your own page.

Single points of failure

So far, every third party answered. What if one doesn't? In 2010, Steve Souders named this a frontend single point of failure. It's one file that can take down the whole page if it never arrives. His example was a script from another site, in the head. If that site is overloaded, he wrote, "the entire page is blank".

Pat Meenan showed how to test for it. When a third party fails, he wrote, "the servers usually become unreachable and requests time out." So a server that says "no" at once isn't a fair test. You need one that never answers. He set up a blackhole server: a server that drops everything sent to it.

The course made its own. It made the consent banner's server take each connection, then say nothing at all. Then it loaded Meera's page with the banner's tag written two ways:

The banner's tag, two waysHTML
<script src="https://consent.example/banner.js"></script>        <!-- 1. plain, as pasted -->
<script src="https://consent.example/banner.js" async></script>  <!-- 2. with async -->
  • Line 1 is the tag as Ravi pasted it: a plain script.
  • Line 2 is the same tag with async. Nothing else on the page changed.
Asha's screen while consent.example never answered, in real frames, at five moments. Plain script: at 1,000 ms, A white screen: nothing drawn yet. at 2,194 ms, A white screen: nothing drawn yet. at 15,000 ms, A white screen: nothing drawn yet. at 30,707 ms, The page: the shop's name, the mint tea photo, the words and the button. at 31,973 ms, The page, with the orange chat bubble in the corner. With async: at 1,000 ms, A white screen: nothing drawn yet. at 2,194 ms, The page: the shop's name, the mint tea photo, the words and the button. at 15,000 ms, The page, with the orange chat bubble in the corner. at 30,707 ms, The page, with the orange chat bubble in the corner. at 31,973 ms, The page, with the orange chat bubble in the corner.1,000 ms2,194 ms15,000 ms30,707 ms31,973 msPlainscriptA white screen: nothing drawn yet.A white screen: nothing drawn yet.A white screen: nothing drawn yet.The page: the shop's name, the mint tea photo, the words and the button.The page, with the orange chat bubble in the corner.WithasyncA white screen: nothing drawn yet.The page: the shop's name, the mint tea photo, the words and the button.The page, with the orange chat bubble in the corner.The page, with the orange chat bubble in the corner.The page, with the orange chat bubble in the corner.
Real frames from one of the two loads of each page. With the plain script, the screen stayed white until Chrome gave up on banner.js. With async, the page came on time. Neither page ever showed the banner.

With the plain script, Asha's screen stayed white for half a minute. With async, the page came at 2,192 ms. That's even sooner than on a normal day, when it came at 2,920 ms. There was no banner code to run. Here are the times:

The banner's server never answeredPop out in the code editor (opens in a new tab)JavaScript
// The banner's server never answered. Times in ms after Asha opened the page.const plain = { waited: 30012, photo: 30705 };const withAsync = { waited: 30007, photo: 2192, loadEvent: 30627 }; console.log(`Plain script: Chrome waited ${plain.waited} ms for banner.js. The photo came at ${plain.photo} ms.`);console.log(`With async: Chrome waited ${withAsync.waited} ms for banner.js. The photo came at ${withAsync.photo} ms.`);console.log(`With async: the load event came at ${withAsync.loadEvent} ms.`);
  • Line 2 holds the plain page: how long Chrome waited for banner.js, and when the photo came. Line 3 holds the page with async, and when its load event came.
  • Lines 5 and 6 print Plain script: Chrome waited 30012 ms for banner.js. The photo came at 30705 ms. and With async: Chrome waited 30007 ms for banner.js. The photo came at 2192 ms.
  • Line 7 prints With async: the load event came at 30627 ms.

On the plain page, the async scripts below the banner waited too. The preload scanner reads ahead in the HTML, so Chrome fetched the tag manager's container early. It arrived at 2,376 ms. But a script can only run once the parser has reached its tag. So the container ran after the banner failed, and asked for analytics.js at 30,785 ms. One plain script held back every script under it.

Real-life analogyWaiting by the phone to open the shop

A shop won't open until the bakery calls to say the bread is coming. One morning, the bakery's phone line is dead. The owner waits by the phone. After half an hour, she gives up and opens. Next door, the café opened on time. It just takes the bread when it comes.

In real life: Waiting for the call before opening
In web performance: A plain script in the head: nothing shows until it's done
In real life: The dead phone line
In web performance: A third party's server that never answers
In real life: Giving up after half an hour
In web performance: Chrome giving up after 30 seconds
In real life: The café that opens on time
In web performance: The same script, with async

Both pages waited about 30 seconds. That number comes from Chrome's own code. Every new HTTPS connection starts with a short exchange that sets up its encryption, called the TLS handshake. Chrome gives it 30 seconds, then gives up. The course's server took the connection, but never did its part of the handshake.

A real blackhole is worse. It never even takes the connection. Then Chrome waits for the operating system to give up. Chromium's code says that takes "anywhere from 20 seconds to 190 seconds", and it stops waiting at 4 minutes. web.dev says the wait "can be anywhere from 10 to 80 seconds".

With async, the page didn't wait. But one thing still did: the load event. It came at 30,627 ms. The HTML standard makes the load event wait for every async script still on its way. So anything that waits for load waited half a minute too. Here, that was four of Meera's tags. Why it matters: async keeps the page on screen, but it doesn't make a dead server free.

Move through both pages, second by second:

Playground: the banner's server never answers
The same page, with the banner's tag written two ways
  1. Banner's server down, plain scriptA white screen: nothing drawn yet.a white screen, drawn at 13 msNothing has happened yet.
  2. Banner's server down, asyncThe page: the shop's name, the mint tea photo, the words and the button.the page, drawn at 2,194 msSo far: the page appeared.
Try it yourself
Asha's screen at 3,000 ms

Move the slider. Each frame is the last one Chrome had drawn by then, with the time it was drawn. Under it: what had happened by then.

Chrome 157 on a phone's screen, DevTools' Slow 4G, a CPU 4 times slower.

Which way answers which question?

Each question below needs one of the three ways of measuring. Sort each one into the way that answers it:

Which way answers which question?
  • How many KB did chat.example send?
  • How many requests went to stats.example?
  • Which third party ran longest on the main thread?
  • Whose code ran inside one long task?
  • Would the photo appear sooner without the A/B test?
  • How much sooner would the load event fire without the tag manager?
Try it yourself
0 of 6 correct

Sort each question by the way of measuring that answers it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "A third party costs as much as its file weighs." No. The chat widget sent the most bytes. But the ad script ran the longest, and the A/B test held back the photo the most.
  • "A tag manager is light. It only loads other tags." No. Its own code ran for 292 ms. With the tags it added, taking it out cut 791 ms of blocking time.
  • "async makes a third party free." No. The ad script was added like an async script, and still ran for 681 ms. And the load event waits for every async script.
  • "If a third party is down, its script just fails fast." No. A server that never answers made Chrome wait 30 seconds. One that drops every packet can take minutes.
  • "Lower blocking time means a faster page." No. Hiding the page lowered the blocking time, because it pushed the first paint later.
  • "DevTools knows every third party by name." No. It names the ones in Third Party Web. Any other site is named after its domain, like tags.example.

Practice exercises

Answer each one first. Then open the hints if you need them.

Exercise 1 · Warm-upAnother shop's biggest third party

Another shop's third parties are listed below. What does the first line print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
// Another shop's "3rd parties" insight: main-thread time (ms) and bytes.
const parties = [
  { name: "reviews.example", ms: 320, bytes: 180000 },
  { name: "pixel.example", ms: 40, bytes: 900 },
  { name: "survey.example", ms: 510, bytes: 42000 },
];
const byTime = [...parties].sort((a, b) => b.ms - a.ms);
const byBytes = [...parties].sort((a, b) => b.bytes - a.bytes);
console.log(byTime[0].name);
console.log(byBytes[0].name);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upRequests without the ad tags

    Block the six ad tags in the block lab. How many requests does the page make?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeBlocking time and the first paint

      The same two long tasks, counted from two different first paints. What does the second line print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      // Blocking time counts the part of each long task past 50 ms, from the first paint on.
      const tasks = [{ start: 1000, duration: 300 }, { start: 2500, duration: 200 }];
      const blockingTime = (firstPaint) =>
        tasks.reduce((sum, task) => {
          const from = Math.max(task.start, firstPaint);
          const to = task.start + task.duration;
          return to - from > 50 ? sum + (to - from - 50) : sum;
        }, 0);
      console.log(blockingTime(900));
      console.log(blockingTime(2000));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeThe chat bubble, on the plain page

        With the banner's server down and its plain script, when was the chat bubble first drawn, in ms?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeOne long task in the trace

          Open the course's trace of the page with every tag in Chrome's Performance panel. Find the longest task on the main thread. Which event is it handling?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeLook up a tag before adding it

            Meera wants to add Hotjar. In which category does the Third Party Web dataset put it?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask which cost it's about: bytes, main-thread time, or risk.

              The cost of third parties quiz · 8 questionsScore: first tries count
              1. Question 1 of 8Leaf and Kettle's page lives at shop.example. Which of these is a third party?

                Choose an answer to see the explanation.

              2. Question 2 of 8Taking out the tag manager cut more blocking time than its own code ran for. Why?

                Choose an answer to see the explanation.

              3. Question 3 of 8The A/B test's own code ran for less time than the ad script's. Why did taking it out bring the photo sooner than taking out anything else?

                Choose an answer to see the explanation.

              4. Question 4 of 8Without the A/B test, the page's blocking time went up. Why?

                Choose an answer to see the explanation.

              5. Question 5 of 8A plain script in the head comes from a server that accepts the connection and never answers. What does the visitor see?

                Choose an answer to see the explanation.

              6. Question 6 of 8With async on the banner's script, the page appeared on time while its server was down. What still waited?

                Choose an answer to see the explanation.

              7. Question 7 of 8What does an entity's Average Impact in the Third Party Web dataset measure?

                Choose an answer to see the explanation.

              8. Question 8 of 8What does this code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const requests = [
                  { site: "tags.example", kb: 70 },
                  { site: "stats.example", kb: 45 },
                  { site: "shop.example", kb: 13 },
                  { site: "stats.example", kb: 1 },
                ];
                const fromOthers = requests.filter((request) => request.site !== "shop.example");
                console.log(fromOthers.reduce((sum, request) => sum + request.kb, 0));

                Choose an answer to see the explanation.

              Key takeaways

              • Third parties are code and files from other companies' sites. More than nine in ten pages have them.
              • Each one costs bytes, main-thread time and risk. The page's total doesn't say which one costs most.
              • Split the cost by site: the Network panel's 3rd-party filter, and the Performance panel's 3rd parties insight.
              • Then take one away and compare. On Meera's page, the tag manager cost the most blocking time. The A/B test cost the most time to the photo.
              • A tag manager's cost includes every tag it adds. The HTML doesn't show them.
              • Third Party Web gives each service's Average Impact, so you can compare before you add one.
              • A plain script from a server that never answers kept Asha's screen white for 30 seconds. With async, only the load event waited.

              Remember the one-liner.
              Measure each third party by itself: what it downloads, how long it runs, and what happens when its server is down.

              Sources

              The facts on this page come from these docs, datasets and source code:

              Coming next: Loading third parties well. You've measured what each tag costs. Next, how to load them so they cost less, or don't load in the browser at all.

              CompleteFrontend Clear concepts. Working examples.