How to Isolate Ad Scripts from the Main Thread

This guide covers the heaviest category in Third-Party Script Performance, within JavaScript Bundle Optimization & Code Splitting. Programmatic advertising runs header-bidding auctions, identity resolution, viewability measurement and brand-safety checks in the page's JavaScript context, then renders creatives that load their own scripts. On ad-supported publishers, ad tech regularly accounts for the majority of main-thread time and requests.

Removing ads is not an option for businesses that depend on them, but much of the cost is structural rather than inherent: auctions running before content renders, creatives injected without reserved space, every slot on a long article requested at load, and creative code executing in the same thread as the page. Isolation — in time (lazy loading), in space (reserved slots) and in execution context (cross-origin iframes) — reduces the cost substantially while preserving revenue.

Where ad cost lands and how to contain it Layers of display advertising cost from auction scripts to creatives, each paired with its containment technique. Where ad cost lands and how to contain it Ad library + auction prebid / GPT on your main thread — defer until after LCP, lazy-load slots Slot rendering creative inserted into the page — reserve size to prevent CLS Creative code runs inside an iframe — cross-origin keeps it off your event loop when isolated Measurement scripts viewability and verification — scope to visible slots only

Rapid Diagnosis

  • Attribute main-thread time by domain in a throttled trace; sum ad-tech domains (ad servers, bidders, verification vendors).
  • Check CLS sources. In the Layout Shifts track, shifts attributed to ad containers or content below them indicate unreserved slots.
  • Check slot loading. Are below-the-fold slots requested at load? GPT's enableLazyLoad and similar settings control this.
  • Check frame isolation. Are creatives rendered in cross-origin iframes (SafeFrames) or friendly (same-origin) iframes that share your process and can run on your main thread?

Root Cause Analysis

1. Auctions on the critical path. Header bidding waits for many bidders before requesting ads, and its scripts parse and run during page load.

2. Unreserved slot sizes. Creatives of varying sizes insert into collapsed containers, pushing content and producing large layout shifts.

3. Same-origin creative execution. Friendly iframes and direct DOM injection let creative scripts run in your process and event loop, competing with your interactions.

4. Eager loading of all slots. A long article with ten slots requests all ten auctions at load, even though most users never scroll to most of them.

Main-thread time on an article page by ad setup (mid-tier phone) Bar chart of main-thread time attributed to advertising on an article page across four increasingly isolated setups. Main-thread time on an article page by ad setup (mid-tier phone) All slots at load, friendly iframes 1840ms + lazy-load slots 960ms + auction after LCP 870ms + SafeFrame creatives 610ms

Step-by-Step Resolution

1. Reserve slot sizes

Give every ad container a minimum height equal to the most common creative size for that slot (or the largest, if you prefer no shift at all), and avoid collapsing empty slots above content.

css
.ad-slot--leaderboard { min-height: 90px; }
@media (max-width: 727px) { .ad-slot--leaderboard { min-height: 50px; } }
.ad-slot--mpu { min-height: 250px; }
/* trade-off: reserving the largest possible size wastes space when a smaller
   creative fills; reserving the most common size risks a shift for larger ones.
   Use your ad server's fill data to choose per slot. */

Expected outcome: ad rendering stops shifting content; see debugging CLS caused by dynamic ad injection.

2. Lazy-load below-the-fold slots

javascript
googletag.cmd.push(() => {
  googletag.pubads().enableLazyLoad({
    fetchMarginPercent: 200,      // request when within 2 viewports
    renderMarginPercent: 100,     // render when within 1 viewport
    mobileScaling: 2.0,           // larger margins on mobile (faster scrolling)
  });
  googletag.enableServices();
});
// trade-off: smaller margins save more work but risk empty slots when users
// scroll quickly, which can reduce viewability and revenue. Tune with your
// ad operations team using viewability data.

Expected outcome: only slots near the viewport run auctions and render; main-thread time at load roughly halves on long pages.

3. Start the auction after the main content has rendered

Load the ad library and run header bidding after LCP (or after load), unless an above-the-fold slot is the business priority. Most revenue comes from slots the user sees after content appears anyway.

4. Render creatives in cross-origin iframes

Prefer SafeFrame / cross-origin rendering for creatives. With site isolation in modern browsers, cross-origin iframes can run in separate processes, keeping creative JavaScript off your page's main thread and away from your interactions.

Creative rendering modes compared Comparison of direct injection, friendly iframes and cross-origin iframes for rendering ad creatives. Creative rendering modes compared Rendering mode Shares your main thread Can touch your DOM Rich media features Direct DOM injection yes yes all Friendly (same-origin) iframe yes yes, via parent all Cross-origin iframe (SafeFrame) isolated when site-isolated no via API

Verification

Compare traces before and after on the same article at the same scroll depth: main-thread time attributed to ad domains should fall, layout shifts from ad containers should disappear, and the interactions you script (opening a menu, tapping a share button) should no longer overlap ad tasks. In the field, compare INP and CLS p75 on ad-supported templates and monitor revenue metrics (fill rate, viewability, RPM) with the ad operations team.

Worked Example: A News Publisher

A news publisher's article template had nine ad slots, all requested at load with friendly-iframe rendering, and a header-bidding wrapper with eleven bidders. Field data: INP p75 380ms, CLS p75 0.21. The team reserved slot sizes from fill data, enabled lazy loading with margins tuned for mobile scrolling, moved the bidding wrapper to load after LCP, cut bidders to the seven that produced 97% of revenue, and switched to SafeFrame rendering where demand partners supported it. Six weeks later: INP p75 210ms, CLS p75 0.04, LCP p75 improved by 600ms, and RPM was within 2% of before — viewability had risen because ads now rendered where users actually were.

Common Mistakes

  • Collapsing empty slots above content. An unfilled slot that collapses after layout shifts everything below it.
  • Lazy-loading without mobile tuning. Mobile users scroll faster; margins that work on desktop leave mobile slots empty.
  • Adding bidders without measuring. Each bidder adds script and network cost; prune by revenue contribution.
  • Testing without ads. Ad blockers on developer machines hide the problem entirely.

Edge Cases

Sticky and anchor ads. These overlay content and usually do not shift layout, but their scripts still run and they can obscure the LCP element on small screens.

Video ads. Outstream video players are heavy; lazy-load them strictly and never above the fold on mobile.

Ad refresh. Refreshing slots on a timer re-runs auctions and creative code repeatedly during the visit, adding main-thread work long after load. Limit refresh to visible slots and reasonable intervals.

Consent states. Ad behaviour differs before and after consent; measure both, as non-consented sessions may load lighter stacks.

FAQ

Do cross-origin iframes always run in a separate process?

With site isolation (default on desktop Chromium and increasingly on Android), cross-site iframes run in separate renderer processes. On devices or browsers without full isolation, they may share a process but still cannot touch your DOM. Either way they are better contained than same-origin frames.

Will delaying the auction reduce revenue?

It can reduce above-the-fold impressions slightly. In practice many publishers see neutral or positive revenue after performance work, because faster pages retain users longer and viewability improves. Test with a holdout group and measure.

Can ad code run in a Web Worker?

Parts of header bidding logic could in principle, and some vendors experiment with it, but most ad libraries assume DOM access. Isolation via iframes and timing via lazy loading are the practical levers today.

How do I attribute INP problems to specific ad vendors?

Collect LoAF script attribution in RUM and group by script origin. Vendors whose scripts appear in INP frames are the ones to prune, defer or isolate first.

Should ad slots use content-visibility or containment?

Containment on the slot container (contain: layout) limits how far a creative's layout changes propagate, which can reduce layout cost. Avoid content-visibility: auto on slots themselves: it can interfere with viewability measurement, which ad partners rely on for billing.