Loading third parties well
Keep every tag and get a fast page. Load tags after the page, behind a facade or after consent. Self-host them, run them in a worker, or tag on a server.
- 01Choose when each tag loadsWith the page, after the load event on an idle moment, on a tap behind a facade, or only after the visitor says yes.
- 02Choose where its code runsOn the main thread, in a web worker with Partytown, or on your own server with server-side tagging.
- 03Check what each fix really changedCompare the photo, the blocking time, when the phone is free, the bytes and the connections, and spot what a fix only moved.
Every tag, and Asha's page back
Ravi has lesson 24.1's numbers on his desk. Meera's tags kept Asha's phone busy for over a second. The A/B test and the consent banner held back her photo. Ravi could delete the tags. But Meera needs every one of them. The ads bring in customers, and the analytics tell her which teas sell.
So Ravi asks a harder question. Can the shop keep every tag Meera needs, and still give Asha her page back?
He tries six fixes on 24.1's page, one at a time. Then he builds one page with all of them: Ravi's page. The course loaded each page three times on "Asha's phone". That's Chrome 157 on a phone's screen, with a CPU 4 times slower. The network has Slow 4G's round trip and speed. But it's the network from lesson 16.3. It delays each piece of data, as a real phone line does. So every new connection pays for its handshakes, and the times aren't quite 24.1's.
Here is what Asha saw:
On 24.1's page, the screen stayed white for over 2 seconds. Then the page came, with the banner. The chat bubble came at 4.4 seconds. On Ravi's page, the photo and the chat bubble came at 1.5 seconds. The banner came a moment later.
Here are the numbers behind those screens:
// Asha's phone: the median of three loads of each page.const before = { photo: 2270, blocking: 1134, freeFrom: 4478, kb: 364.1 };const ravis = { photo: 1488, blocking: 505, freeFrom: 2040, kb: 73.8 }; console.log(`Photo on screen: ${before.photo} ms, then ${ravis.photo} ms`);console.log(`Blocking time: ${before.blocking} ms, then ${ravis.blocking} ms`);console.log(`Phone free from: ${before.freeFrom} ms, then ${ravis.freeFrom} ms`);console.log(`Downloaded: ${before.kb} KB, then ${ravis.kb} KB`);- Line 1 is a comment. Each number is a median of three loads: the middle one, once they're sorted.
- Line 2 holds 24.1's page.
photois when the photo appeared, in milliseconds (ms).blockingis the blocking time from 24.1: how long the phone was too busy to answer a tap, after the first paint. freeFromis new. A long task holds the main thread, where the page's scripts run, for more than 50 ms.freeFromis when the page's last long task ended. After that, a tap got a quick answer.kbis everything the phone downloaded.- Line 3 holds Ravi's page.
- Lines 5 and 6 print
Photo on screen: 2270 ms, then 1488 msandBlocking time: 1134 ms, then 505 ms. - Lines 7 and 8 print
Phone free from: 4478 ms, then 2040 msandDownloaded: 364.1 KB, then 73.8 KB.
The photo came 782 ms sooner. Blocking time fell by more than half. The phone was free 2.4 seconds sooner. And it downloaded about a fifth of the bytes. Yet Meera lost no tag. Why it matters: you don't have to choose between tags and a fast page. You choose how each tag loads.
A small café has one barista. If she also unpacks deliveries, does the books and opens the post at the counter, the queue waits. So the owner moves the jobs. Deliveries wait until after the rush. The books go to the back office. The bills go to the accountant.
- In real life: The barista at the counter
- In web performance: The main thread
- In real life: The customers in the queue
- In web performance: Asha's taps, and the page's own work
- In real life: Unpacking the delivery after the morning rush
- In web performance: Loading a tag after the page
- In real life: The books, done in the back office
- In web performance: A tag running in a worker
- In real life: Bills, posted to the accountant
- In web performance: Reports sent through the shop's server
For each third party, choose when it loads and where its code runs. It can load with the page, after it, on a tap, or after a yes. It can run on the main thread, in a worker, or on your own server. Pick the latest moment, and the place furthest from the main thread, that still does the job.
Lesson 23.1 showed async and defer, and lesson 19.2 showed facades. The Next.js lesson Scripts and third-party code showed next/script. The system design lesson Third-party scripts and supply chain covers keeping this code safe. Here, you'll answer six questions:
- Can a tag wait until the page has loaded? Loading after the main content.
- What if most visitors never use it? A facade for the chat widget.
- Should the files come from the shop's own site? Self-hosting third-party code.
- Can a tag run off the main thread? Running tags in a web worker.
- Can the phone skip the tags altogether? Server-side tagging.
- What about Asha's answer to the banner? Waiting for consent.
Loading after the main content
The first fix is the simplest. The tag manager and the chat widget don't help Asha see the tea. So Ravi loads them after the page.
When is "after the page"? The browser fires the load event once the page and all its files have arrived. Ravi waits for that. Then he waits for one more thing: a free moment. requestIdleCallback() runs a function when the browser has nothing else to do. The spec calls that an idle period, and keeps each one to 50 ms at most. It's like a shop assistant who restocks the shelves only when nobody is at the till.
const whenIdle = window.requestIdleCallback || ((run) => setTimeout(run, 1));addEventListener("load", () => { whenIdle(() => { for (const src of ["https://tags.example/container.js?id=LK-12", "https://chat.example/widget.js"]) { const script = document.createElement("script"); script.src = src; document.body.append(script); } }, { timeout: 2000 });});- Line 1 makes
whenIdle. It'srequestIdleCallbackwhere the browser has it. Safari has it only in its preview, behind a setting. There,setTimeoutruns the function after 1 ms instead. - Line 2 waits for the load event. Line 3 then asks for a free moment.
- Lines 4 to 8 add two script tags: the tag manager's container and the chat widget.
- Line 9 sets a
timeout. If no free moment comes within 2 seconds, the function runs anyway. MDN strongly recommends a timeout for work that must happen.
Next.js's lazyOnload strategy works much the same way. Its code waits for the load event, then for an idle moment.
The course loaded this page three times. Three things happened:
- The load event came much sooner: at 2,256 ms instead of 4,407 ms. Chrome asked for the container just after it.
- The photo came at the same time: 2,269 ms. The tags weren't what held it back.
- The phone was busy for longer. It was free from 5,347 ms, not 4,478 ms. Blocking time even rose a little, from 1,134 ms to 1,195 ms.
Why? Loading later doesn't make a tag's work smaller. It moves it. Each block below is one long task, on a time line:
Look at the first two rows. Their first three blocks sit in the same place: the A/B test and the banner. The rest belong to the tags and the chat. On the second row, they start later and end later. ads.js still ran for 401 ms in one task. An idle moment can start a script. It can't cut it into pieces.
Why it matters: load a tag later when the page needs the network and the phone first. Don't expect it to cost less. And watch for one more cost. web.dev notes that analytics usually loads early, so it doesn't miss visits. A visitor who leaves before the tags arrive is never counted.
Where the work lands matters to Asha's taps. Move the tap, and see what it meets on each page:
- 24.1's page, as it wasA long task runs until 4,220 ms. The tap waits 220 ms.
- Tags and chat after the load eventNo long task. The tap gets an answer quickly.
- Tags in a worker (Partytown)No long task. The tap gets an answer quickly.
- Server-side taggingNo long task. The tap gets an answer quickly.
- Ravi's pageNo long task. The tap gets an answer quickly.
Worked out from each page's recorded long tasks, on the middle of three loads. A tap that lands in a long task waits at least until that task ends.
A facade for the chat widget
Most visitors never open the chat. Yet every one of them downloaded the widget and ran it. Lesson 19.2 fixed that with a facade: a light copy of a widget that looks the same, and loads the real one only on a tap. Remember the dummy phone in the shop window. Here is the chat's facade:
<button class="chat-bubble" type="button" aria-label="Chat with Ravi"></button><script> document.querySelector(".chat-bubble").addEventListener("click", (event) => { const widget = document.createElement("script"); widget.src = "https://chat.example/widget.js"; document.body.append(widget); event.currentTarget.remove(); }, { once: true });</script>- Line 1 is a button that looks like the widget's bubble.
- Lines 3 to 8 run on the first tap only, because of
once: trueon line 8. - Lines 4 to 6 add the real widget's script tag. Line 7 removes the facade.
Nobody tapped in these loads, so widget.js never came. The page downloaded 263.3 KB instead of 364.1 KB. Blocking time fell from 1,134 ms to 782 ms. The widget's 441 ms of main-thread work was gone. On Ravi's page, the bubble even came sooner. It's part of the HTML, so it came with the photo at 1.5 seconds, not at 4.4 seconds. Then the banner covered it until Asha answered.
Why it matters: a facade moves a widget's cost onto the few people who use it. They still wait after their tap. Lesson 19.2 measured that wait.
Self-hosting third-party code
Every third party's file comes from its own site. Each new site needs a new connection, with its own handshakes. Lesson 16.3 measured about 300 ms of handshakes for each one, on this network. Self-hosting means copying the file onto your own site, and serving it from there.
You can phone the bus company each time you need the timetable. Or you can keep a copy on the fridge. The copy is quicker, and it works when their line is down. But when the timetable changes, you must fetch a new copy yourself.
- In real life: Phoning the bus company
- In web performance: A new connection to the vendor's site
- In real life: The copy on your fridge
- In web performance: The copy in /vendor/ on the shop's site
- In real life: Their phone line is down
- In web performance: The vendor's server never answers
- In real life: The timetable changes in May
- In web performance: The vendor updates its script
Ravi copied the six vendor files into /vendor/ on shop.example. The copy of the container asks for the copies of analytics.js and ads.js. Their reports still go to stats.example and ads.example. And the chat still asks chat.example for its settings. Those are the vendors' own servers, and they stay.
The phone now connected to 4 sites instead of 7, and opened 9 connections instead of 14. The banner's copy arrived at 1,117 ms, not 1,324 ms. The scripts ran sooner, so the load event came 492 ms sooner, and the phone was free 493 ms sooner.
But the photo came at about the same time: 2,235 ms. And the photo's own file arrived later, at 1,117 ms instead of 745 ms. It now shared the shop's connection with every big script. The page was still hidden then, so Asha saw no difference. Why it matters: self-host what the page needs early. Load the big ones later, or not at all.
Self-hosting has three more effects:
- The vendor's outage can't reach the page. The self-hosted page sent no request to consent.example. So 24.1's 30 seconds of white screen can't happen here.
- The copy doesn't update itself. web.dev warns that self-hosted scripts "won't get automatic updates". Meera's container changes each time she edits a tag. The copy changes only when Ravi copies it again. Check each vendor's terms too.
- DevTools can't tell the copies apart. Its 3rd parties insight groups work by the site a file came from. So it put all the copies' work under shop.example, as first-party time: 2,562 ms. Keep your own list of what you copied.
Running tags in a web worker
Loading later moved the tags' work in time. Could it leave the main thread instead? The main thread is the one line of work where the browser runs the page's scripts and draws the page.
A web worker is a second thread for JavaScript. It runs a script file next to the page, so its work doesn't block taps. But a worker can't touch the page. It has no document, so it can't add a script tag or read the screen. It talks to the page only by sending messages.
Tags were written for the page, though. Partytown bridges the gap. It's an open-source library that runs third-party scripts in a worker. When a script asks about the page, Partytown carries the question to the main thread. The script waits for the answer, so it works as written.
A cook works in the back, away from the customers. When she needs to know how many plates table 4 wants, a runner asks the waiter at the front. The cook waits until the runner comes back. The cooking never blocks the front of the shop.
- In real life: The cook in the back kitchen
- In web performance: A tag's code, in the worker
- In real life: The waiter at the front
- In web performance: The main thread, with the page
- In real life: The runner
- In web performance: Partytown, carrying each question
- In real life: The cook waiting for the reply
- In web performance: The tag waiting until the page answers
To carry questions, Partytown uses a service worker: a script that sits between the page and the network. Its docs say a faster way, Atomics, works only on pages that send two extra headers. The worker page didn't, so Partytown used its service worker. Ravi changed three lines:
<script>partytown = { lib: "/~partytown/" };</script><script>/* Partytown's snippet */</script><script type="text/partytown" src="https://tags.example/container.js?id=LK-12"></script>- Line 1 tells Partytown where its own files are on the shop's site.
- Line 2 stands for Partytown's snippet: a short script copied from its package. It starts Partytown once the HTML has been read.
- Line 3 gives the container
type="text/partytown". The browser doesn't run a script with a type it doesn't know. Partytown finds it, and runs it in the worker. The scripts it adds go there too.
The course used Partytown 1.0.5. The container, analytics.js and ads.js all ran in the worker. DevTools' 3rd parties insight no longer listed them on the main thread. Blocking time fell from 1,134 ms to 345 ms. The phone was free from 3,404 ms. Look at the third and fourth rows of the picture above. The tags' blocks moved to the worker's row: 1,065 ms of long tasks there.
The worker had costs of its own:
- Bytes and connections. Partytown's own files came too. The page downloaded 403.1 KB, 39.0 KB more than 24.1's page, over 18 connections.
- Late reports. Partytown starts once the HTML has been read. So the first report reached stats.example at 4.3 seconds, not 2.9.
- Not every script fits. Partytown's docs say it suits tags like tag managers, which mostly send data. Scripts that build parts of the page may not. In Partytown, a script can't stop a click with
preventDefault(). And code that runs as the visitor leaves may never run.
Next.js offers this as its worker strategy. Its docs call it experimental, and say it doesn't work with the App Router yet. Why it matters: a worker frees the main thread for Asha's taps. Test each tag you move, and keep a way back.
Server-side tagging
The worker moved the tags' work to another thread. Server-side tagging moves it off the phone. The page sends each event once, to the shop's own server. The server then sends each vendor what it needs.
On holiday, you don't walk to the post office with each letter. You hand the bundle to the front desk. They post each letter to its address. You decide what goes in each envelope.
- In real life: Your bundle of letters
- In web performance: One event, sent once from the page
- In real life: The hotel's front desk
- In web performance: The shop's own server
- In real life: Each letter, posted to its address
- In web performance: Each vendor's request, sent by the server
- In real life: Not walking to the post office
- In web performance: No vendor code on Asha's phone
Google Tag Manager calls the server's part a server container. Its docs say it "doesn't run in the user's browser or on their phone". They also call a path on your own site, like /metrics, the best practice. Ravi built a stand-in. On the page, his own small tag:
// tags.js: the shop's own tag. It sends each event to the shop's server.const send = (name) => navigator.sendBeacon("/metrics", name);const start = () => { send("page_view"); if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => send("tea_seen")); else send("tea_seen"); if (document.readyState === "complete") send("page_loaded"); else addEventListener("load", () => send("page_loaded"));};if (window.consent) window.consent.whenGranted(start);else start();- Line 2 makes
send.navigator.sendBeacon()sends a small report as a POST request, in the background. MDN says it's made for analytics. Each report goes to/metrics, on the shop's own site. - Lines 3 to 9 make
start. Line 4 reports the page view at once. Lines 5 and 6 report that the tea of the week was seen, at DOMContentLoaded. Lines 7 and 8 report the page as loaded, at the load event. - Lines 10 and 11 wait for Asha's yes, if the page asks for one. The section on consent comes back to that.
On the server, /metrics turns each event into the requests the container's tags would have sent:
The phone sent 11 requests to 4 sites, instead of 23 to 7. It downloaded 180.7 KB, about half. tags.js was 225 bytes. The container, analytics.js and ads.js had been 172.5 KB. Blocking time fell to 345 ms, as with the worker. And the vendors still got all 13 requests.
Server-side tagging has costs too:
- A server to run. Google's docs suggest at least 3 servers for live traffic, at about $30 to $50 each a month.
- Only some tags can move. The chat and the A/B test change the page, so they need code on it.
Why it matters: the cheapest tag for the phone is one that doesn't run on the phone. Move reports to your server when you can run one.
Waiting for consent
One third party asks Asha a question: the consent banner. A consent manager shows the banner and keeps her answer. The system design lesson explains the rules in the EU and the UK: tags that track her need her yes first. But on 24.1's page, every tag ran before she answered.
At the till, the tea shop asks if you'd like a loyalty card. Only after you say yes does it write down what you buy. If you say no, nothing is written. Nobody keeps notes while you decide.
- In real life: "Would you like a loyalty card?"
- In web performance: The consent banner
- In real life: Writing down what you buy
- In web performance: Tags that track Asha
- In real life: Waiting for your answer
- In web performance: Tags waiting for consent
- In real life: "No, thanks": nothing is written
- In web performance: No tracking tag ever loads
Ravi's page keeps a small note of Asha's answer, and a list of what's waiting for it:
const waitingForYes = [];window.consent = { granted: false, whenGranted(run) { if (this.granted) run(); else waitingForYes.push(run); }, answer(choice) { this.granted = choice === "granted"; if (this.granted) waitingForYes.splice(0).forEach((run) => run()); },};- Line 1 makes an empty list. Line 2 makes
window.consent, and line 3 starts it with no yes. - Lines 4 to 7 are
whenGranted. With a yes, it runs the function at once. Without one, it puts it on the list. - Lines 8 to 11 are
answer. The banner calls it when Asha taps a button. Line 10 runs everything on the list, but only after a yes.
Then the tag manager waits on that list:
consent.whenGranted(() => {
const script = document.createElement("script");
script.src = "https://tags.example/container.js?id=LK-12";
document.head.append(script);
});Lines 2 to 4 add the container's script tag. They run only after Asha says yes. The course gave the banner working buttons, and tapped Accept all about 5 seconds after asking for the page. It tried two pages: tags at once, and tags waiting for a yes. It counted the requests that reached the three tracking sites, the ones that track Asha: tags.example, stats.example and ads.example.
Before the tap, the two pages were very different:
- With tags at once, 16 requests reached tracking sites. Waiting for a yes, none did.
- The phone downloaded 364.3 KB, against 180.6 KB.
- Blocking time was 1,136 ms, against 344 ms.
After the tap, the waiting tags loaded. The first request reached its site at 5,406 ms. Their long tasks ran until 7,880 ms. The banner closed within 22 ms of the tap, on every load. Why it matters: waiting for consent is what those rules ask. It's also the cheapest way to load: nothing before a yes, and nothing ever after a no.
Google's tags can follow the banner in two ways, called consent mode. In basic consent mode, they don't load until the visitor answers, and send no data. In advanced consent mode, they load at once. Until a yes, they send "measurements without cookies". Next.js names consent managers as a fit for its beforeInteractive strategy, which fetches a script before the page's own code.
Move through both pages, and watch the requests add up:
- Tags load at once
page and banner10 requests to tracking sitesAsha hasn't answered yet. - Tags wait for a yes
page, banner and chat0 requests to tracking sitesAsha hasn't answered yet.
Move the slider. Each frame is the last one Chrome had drawn by then, on the middle of three loads. Under it: how many requests had reached the tag manager, analytics and ad sites.
Ravi's page, all together
Ravi's page uses the fixes at once. Here is its head, shortened, and the end of its body:
<style>.ab-hide { opacity: 0 !important; }</style><script>/* hides the page until the A/B test runs, as in 24.1 */</script><script src="/vendor/experiment.js" async></script><script>/* window.consent: the page's note of Asha's answer */</script><script src="/vendor/consent.js" async></script><script src="/tags.js" async></script><script src="shop.js" defer></script><!-- ...the photo, the words and the button... --><button class="chat-bubble" type="button" aria-label="Chat with Ravi"></button><script>/* loads the chat widget on a tap */</script>- Lines 1 and 2 keep the A/B test's snippet: the page stays hidden until the test has run.
- Line 3 loads the A/B test from the shop's own site.
- Line 4 is the note of Asha's answer. Line 5 loads the banner's copy with
async, so it no longer stops the browser reading the page. - Line 6 is tags.js, which waits for a yes. Line 7 is the shop's own script.
- Lines 9 and 10 are the chat's facade.
Only Ravi's page moved the photo much. Lesson 24.1 found what held it back: the A/B test's hidden page, and the banner's plain script. The other fixes left both alone. Ravi's page changes both, and nothing big competes with them on the network. It changes several things at once, so it can't say which change gave how much.
On every page, the first paint and the photo came together. The A/B test's snippet shows the whole page at once. Read each row across: the page, then its medians on Asha's phone.
| Page | Photo on screen | Blocking time | Phone free from | Downloaded | Connections |
|---|---|---|---|---|---|
| 24.1's page, as it was | 2,270 ms | 1,134 ms | 4,478 ms | 364.1 KB | 14 |
| Tags and chat after the load event | 2,269 ms | 1,195 ms | 5,347 ms | 364.2 KB | 16 |
| Chat behind a facade | 2,281 ms | 782 ms | 4,394 ms | 263.3 KB | 12 |
| Self-hosted copies | 2,235 ms | 1,141 ms | 3,985 ms | 351.5 KB | 9 |
| Tags in a worker (Partytown) | 2,291 ms | 345 ms | 3,404 ms | 403.1 KB | 18 |
| Server-side tagging | 2,299 ms | 345 ms | 3,706 ms | 180.7 KB | 5 |
| Ravi's page | 1,488 ms | 505 ms | 2,040 ms | 73.8 KB | 1 |
Each fix moved different numbers. The facade and the server cut bytes. The worker and the server cut blocking time. Self-hosting cut connections. Why it matters: each fix answers its own question. Measure the one you care about.
Pick a page, and compare it with 24.1's:
- Photo on screen2,270 ms
- Blocking time1,134 ms
- Phone free from4,478 ms
- Load event4,407 ms
- Downloaded364.1 KB
- Connections14
- Requests from the phone23
Each number is the median of three loads. In brackets: how far it moved from 24.1's page.
When should each tag load?
Sort each third party by the latest moment it can load and still do its job:
- The consent banner itself
- An A/B test that changes the button before Asha sees it
- A chat widget most visitors never open
- A video player below the photo
- An ad tag that adds Asha to a remarketing list
- A "Was this page helpful?" survey that shows after a minute
Sort each third party by when it should load.
Common mix-ups
- "Loading a tag later makes it cheaper." No. The same work ran later. The phone was busy until 5,347 ms instead of 4,478 ms.
- "An idle callback splits a long script into small pieces." No. It only decides when the script starts. ads.js still ran for 401 ms in one task.
- "A worker makes a tag free." No. Its work ran on the worker's thread instead. And Partytown added 39.0 KB and more connections.
- "A self-hosted script runs faster." No. The copy runs the same code. On the self-hosted page, blocking time stayed at 1,141 ms.
- "Server-side tagging takes every third party off the page." No. Only the reports moved. The chat and the A/B test still need code on the page.
- "A consent banner only has to be shown." No. Unless the tags wait for the answer, they run before Asha chooses. With tags at once, 16 requests reached tracking sites before her tap.
Practice exercises
Answer each one first. Then open the hints if you need them.
This is Safari's fallback from the first section. What's the last line it prints?
const whenIdle = (run) => setTimeout(run, 1); // Safari's fallback
console.log("page");
whenIdle(() => console.log("tags"));
console.log("photo");tags. Lines 2 and 4 run at once, so page and photo print first. The function passed to whenIdle waits for a later task, like a tag loaded on an idle moment.
How many connections did the page with the chat behind a facade open?
12, two fewer than 24.1's page. With no tap, the phone never connected to chat.example.
Asha taps Add to cart at 4,500 ms. On the page whose tags load after the load event, how long does her tap wait?
376 ms. ads.js started at about 4,475 ms on that page, and ran for 401 ms. On 24.1's page, the same tap met no long task: its tags had already run.
At 4,000 ms, how many requests had reached tracking sites on the page whose tags load at once?
10. By then, the container, analytics.js, ads.js and seven pixels had reached tracking sites. Asha hadn't answered yet. On the other page, the count is 0.
Open the course's trace of the worker page in Chrome's Performance panel. ads.js ran one task of about 403 ms. On which thread did it run?
In a worker: Partytown's. Its track sits below Main. That's why ads.js no longer added to the page's blocking time.
In a server container, what does Google call the part that receives each request and turns it into events?
A client. Each request is offered to the server container's clients. The one that understands it turns it into events for the tags.
Check your understanding
For each question, ask two things: when does the tag load, and where does its code run?
Question 1 of 7Ravi loaded the tag manager and the chat widget after the load event, on an idle moment. What happened to their work?
Choose an answer to see the explanation.
Question 2 of 7Five of the fixes didn't move the photo at all. Why not?
Choose an answer to see the explanation.
Question 3 of 7Which kind of third-party script does Partytown suit best?
Choose an answer to see the explanation.
Question 4 of 7After self-hosting, how does DevTools' 3rd parties insight show the copies of the vendors' scripts?
Choose an answer to see the explanation.
Question 5 of 7With server-side tagging, who sends the reports to the analytics and ad sites?
Choose an answer to see the explanation.
Question 6 of 7In Google's basic consent mode, what happens before the visitor answers the banner?
Choose an answer to see the explanation.
Question 7 of 7Ravi uses requestIdleCallback in a page that Safari visitors open too. What should his code do?
Choose an answer to see the explanation.
Key takeaways
- For each third party, choose when it loads and where its code runs.
- Loading after the load event, on an idle moment, gets tags out of the page's way. It moves their work. It doesn't shrink it.
- A facade puts a widget's cost on the visitors who tap it.
- Self-hosting saves connections and outages, but the copies don't update themselves, and they can crowd the line.
- A worker, with Partytown, takes tags off the main thread. Test each one.
- Server-side tagging takes the reports off the phone. The chat and the A/B test still need the page.
- Tags that track the visitor wait for a yes. Nothing loads before it.
Remember the one-liner.
Load each third party as late as it can be, and run it as far from the main thread as it can go.
Sources
The facts on this page come from these docs, specs and source code:
- Efficiently load third-party JavaScript, web.dev.
- Network throttling: request-level and packet-level (Lighthouse v13.6.0), Lighthouse docs.
- Window: requestIdleCallback() method, MDN.
- requestIdleCallback(), W3C Web Performance Working Group.
- Script Component: strategy, Next.js docs.
- script.tsx: lazyOnload waits for load, then an idle moment (Next.js v16.4.6), Next.js source.
- Lazy load third-party resources with facades, Chrome for Developers.
- Web Workers API, MDN.
- Partytown: run third-party scripts from a web worker, QwikDev.
- How does Partytown work, QwikDev.
- Partytown: trade-offs, QwikDev.
- Partytown: Atomics, QwikDev.
- @qwik.dev/partytown 1.0.5, npm.
- An introduction to server-side tagging, Google Tag Manager docs.
- Server-side tagging: overview, Google Tag Manager docs.
- Server-side tagging: same-origin serving, Google Tag Manager docs.
- Navigator: sendBeacon() method, MDN.
- Consent mode overview, Google tag platform docs.
- Browser compatibility data 8.1.8, MDN.
Coming next: Governing third parties. You've loaded each tag well. Next, how to keep it that way as Meera adds more.