Google uses Core Web Vitals (CWV) as a ranking signal. Sites with poor CWV scores get deprioritized in search results, which means less traffic, which means less ad revenue. The irony is that display ads are one of the primary causes of bad CWV scores. Every ad script you add to a page potentially slows it down, shifts the layout, and degrades interactivity.
This creates a tension that every ad-supported publisher faces: you need ads for revenue, but ads hurt the page performance that drives traffic. The good news is that this is a solvable problem. Publishers who implement ads correctly can maintain "Good" CWV scores across all three metrics while running a full ad stack with 4-6 display units, header bidding, and sticky formats.
This guide covers exactly how to do that, with specific techniques for each Core Web Vital.
How Ads Affect Each Core Web Vital
Before fixing the problem, you need to understand precisely how ads cause damage to each metric.
Cumulative Layout Shift (CLS)
CLS measures unexpected visual movement of page elements. The "Good" threshold is 0.1 or below. Ads are the single biggest contributor to CLS on most publisher sites.
How ads cause CLS: When an ad container exists in the page but has no explicit height, the browser initially renders the space as zero-height. When the ad creative loads (often 1-3 seconds after page render), the container expands to fit the ad, pushing all content below it downward. A 300x250 ad loading mid-content creates a 250px shift, which is a massive CLS penalty.
Multi-size ad slots make this worse. If a container can serve either a 728x90 or a 970x250 creative, the browser does not know which size to reserve. When a 970x250 loads into space reserved for 728x90, the 160px height difference causes additional shift.
Typical CLS contribution from ads: 0.05-0.30 depending on the number of ad units and whether size reservation is implemented. Without any mitigation, a page with 4 display ads can easily exceed a CLS of 0.5.
Largest Contentful Paint (LCP)
LCP measures how long it takes for the largest visible element to render. The "Good" threshold is 2.5 seconds or less. Ads affect LCP in two ways:
Render-blocking scripts: If ad scripts load synchronously in the <head>, they block the browser from rendering any content until the scripts finish executing. A Prebid.js bundle (80-150KB) plus Google Publisher Tag (60KB) plus third-party bidder scripts can add 500-1,500ms to initial render time on mobile connections.
Resource contention: Even async ad scripts compete with page content for bandwidth and CPU time. On a 3G mobile connection, ad scripts downloading simultaneously with hero images and fonts delay everything.
Typical LCP impact from ads: 300-1,500ms depending on script loading strategy and connection speed. On fast connections with async loading, the impact is minimal. On slow mobile connections with poorly implemented scripts, it can push LCP from 2.0s to 4.0s+.
Interaction to Next Paint (INP)
INP measures the responsiveness of the page to user interactions (clicks, taps, keyboard input). The "Good" threshold is 200ms or less. Ads affect INP through JavaScript execution:
Main thread blocking: Ad scripts run on the main thread alongside your page's own JavaScript. When a header bidding auction runs (evaluating bids from 6-8 partners, calculating floor prices, setting targeting), it can consume 50-200ms of main thread time. If a user clicks something during this processing window, the browser cannot respond until the script completes.
Continuous ad-related tasks: Viewability tracking, ad refresh timers, and creative rendering all schedule regular tasks on the main thread. Each task is small (5-20ms), but they accumulate and can delay interaction responses.
Typical INP impact from ads: 50-300ms depending on the number of bidders, script efficiency, and device performance. Mobile devices with slower processors are significantly more affected.
The Real-World Cost
A news publisher analyzed their CWV data and found that 62% of their mobile pages failed the CLS threshold, 34% failed LCP, and 28% failed INP. After implementing the techniques in this guide — size reservation, lazy loading, async script management, and yield-to-main scheduling — their pass rates improved to 91% CLS, 82% LCP, and 87% INP within 28 days. Their organic search traffic increased by 8% over the following quarter.
Fixing CLS: Size Reservation
Size reservation is the most impactful single technique for improving CLS on ad-supported pages. The concept is simple: tell the browser exactly how much space each ad will occupy before any ad script runs.
Static Size Reservation
For ad slots that always serve the same size, set CSS min-height and min-width on the container:
For a 300x250 ad container, set min-height: 250px and width: 300px (or max-width: 300px for responsive). The browser reserves this space during initial layout, so when the ad loads, it fills the reserved space without pushing other content.
Multi-Size Reservation
For slots that accept multiple sizes (e.g., 728x90 and 970x250), reserve space for the largest possible creative. If you reserve 250px height and a 90px ad loads, you have 160px of blank space below the ad, which looks slightly awkward but causes zero CLS. Blank space below an ad is always better than layout shift.
Alternatively, use CSS aspect-ratio with a container query approach: set the aspect ratio to match the most common creative size, and use a CSS transition to smoothly resize if a different size loads. A 200ms CSS height transition counts as an "expected" layout change and does not contribute to CLS.
Collapse-on-No-Fill
When an ad slot receives no fill (no ad loads), the reserved space becomes blank. You can collapse the space using googletag.pubads().collapseEmptyDivs(), but this collapse itself causes CLS because content shifts upward. The better approach: only collapse with a smooth CSS transition, and only collapse for below-fold slots where the user will not notice the shift.
For above-fold slots, it is better to always show something — a house ad, a promotional banner, or your own content — rather than collapsing and causing CLS.
Fixing LCP: Async Loading and Prioritization
The goal is to ensure ad scripts never block or delay the rendering of your page's main content.
Async Script Loading
Every ad-related script must use the async attribute: Google Publisher Tag, Prebid.js, Amazon TAM, and any other third-party scripts. Never use defer for ad scripts because deferred scripts execute in order, which means a slow ad script delays all subsequent deferred scripts including your own page JavaScript.
Delayed Initialization
Load ad library scripts in the <head> with async, but delay actually requesting ads until after the LCP element has rendered. A practical approach:
- Load Prebid.js and GPT async in the head
- Define ad units and configuration in an inline script
- Do not call
pbjs.requestBids()orgoogletag.pubads().refresh()until after arequestIdleCallbackor after the LCP element has loaded (detected via aPerformanceObserveron the "largest-contentful-paint" entry)
This gives the browser time to render content before ad auctions consume CPU and bandwidth.
Resource Hints
Use <link rel="preconnect"> for critical ad domains to establish connections early without downloading scripts:
preconnecttosecurepubads.g.doubleclick.net(GPT)preconnecttoc.amazon-adsystem.com(if using Amazon TAM)preconnectto your Prebid Server endpoint (if using server-side bidding)
Do not use preload for ad scripts — this gives them high priority that can compete with your own content resources.
Lazy Loading Below-Fold Ads
The most effective LCP improvement for ad-heavy pages: only request ads for above-the-fold slots on initial page load. All below-fold ad slots should wait until the user scrolls within 1-2 viewports of the slot position.
Implementation: use Intersection Observer to detect when each below-fold ad container approaches the viewport. When triggered, run the Prebid auction and GPT refresh for that specific slot. This reduces initial page load from requesting 5-6 ad auctions to just 1-2, cutting initial JavaScript execution time by 60-70%.
Fixing INP: Main Thread Management
INP is the hardest metric to optimize for ad-supported pages because ad scripts need the main thread, and user interactions need the main thread, and they cannot both have it at the same time.
Yield to Main Thread
The key technique is breaking long ad-related tasks into smaller chunks that yield back to the browser between each chunk. Instead of processing all header bidding responses in a single synchronous block (which might take 80ms), process each bidder's response in a separate task with setTimeout(fn, 0) or the scheduler.yield() API between them. Each individual task takes 10-15ms, and the browser can handle user interactions in the gaps.
Web Workers for Bid Processing
Some header bidding wrappers support moving bid evaluation logic to a Web Worker, which runs on a separate thread entirely. This prevents bid processing from blocking user interactions. The trade-off is added complexity and a small communication overhead between the main thread and worker.
Minimize Third-Party Script Impact
Each demand partner's adapter runs its own JavaScript. Some are well-optimized (5-10ms execution). Others are poorly optimized (50-100ms execution with synchronous XMLHttpRequests, excessive DOM manipulation, or heavy creative rendering). Identify slow bidder adapters by profiling ad auction performance in Chrome DevTools:
- Open DevTools Performance tab
- Record a page load
- Filter for long tasks (50ms+) during the ad auction window
- Identify which bidder scripts are responsible for the longest tasks
- If a bidder consistently causes 100ms+ tasks and its bid rate is low, consider removing it. The INP improvement may be worth more than the incremental revenue.
Before and After: Real Examples
Example 1: Recipe Blog
Before optimization: 5 ad units, all loading on page load, no size reservation, synchronous Prebid initialization.
| Metric | Before | After | Change |
|---|---|---|---|
| CLS (p75) | 0.28 | 0.04 | -86% |
| LCP (p75) | 4.2s | 2.1s | -50% |
| INP (p75) | 340ms | 160ms | -53% |
What changed: Added min-height to all ad containers. Moved Prebid/GPT to async loading. Lazy loaded 3 below-fold ad slots. Added requestIdleCallback delay before initial auction.
Revenue impact: RPM decreased by 3% (from slightly delayed ad loading) but organic traffic increased by 12% over the following 8 weeks due to improved CWV scores. Net revenue increased by 9%.
Example 2: News Publisher
Before optimization: 7 ad units, header bidding with 8 bidders, 2,000ms timeout, no lazy loading.
| Metric | Before | After | Change |
|---|---|---|---|
| CLS (p75) | 0.18 | 0.06 | -67% |
| LCP (p75) | 3.8s | 2.4s | -37% |
| INP (p75) | 280ms | 180ms | -36% |
What changed: Size reservation on all slots. Reduced bidder count from 8 to 6 (removed two slow bidders with low win rates). Reduced timeout from 2,000ms to 1,200ms. Lazy loaded 4 below-fold slots. Moved bid processing to requestIdleCallback chunks.
Revenue impact: Removing two bidders and reducing timeout lowered RPM by 5%. But CWV improvements drove a 15% organic traffic increase over 10 weeks. Total revenue increased by 10%.
The Trade-Off Pattern
In nearly every case study, CWV optimization causes a small short-term RPM decrease (2-8%) from delayed ad loading and reduced auction competition. But the organic traffic gains from better search rankings more than compensate within 4-10 weeks. This is why platforms like WeForAds build CWV optimization directly into their ad delivery, handling the balance automatically so publishers do not have to choose between revenue and performance.
Quick Wins Checklist
If you only implement five things from this guide, make it these:
- Add min-height to every ad container: Match the largest creative size the slot accepts. This alone can cut CLS by 50-80%. Takes 30 minutes to implement.
- Add async to all ad script tags: If any of your ad scripts load without the
asyncattribute, fix that immediately. Takes 5 minutes. - Lazy load below-fold ads: Use Intersection Observer to delay ad requests for slots not near the viewport. Cuts initial ad-related JavaScript execution by 50-70%. Takes 1-2 hours.
- Add preconnect for ad servers: Add
<link rel="preconnect">for doubleclick.net and your primary SSP domains. Saves 100-300ms on ad requests. Takes 10 minutes. - Review your bidder timeout: If your Prebid timeout is above 1,500ms, analyze bid response times and reduce it. Most bids arrive within 800-1,000ms. Takes 1 hour to analyze and implement.
Frequently Asked Questions
Do ads hurt Core Web Vitals scores?
Yes, ads are one of the most common causes of poor CWV scores. They cause CLS when containers resize, slow LCP when scripts block rendering, and degrade INP when JavaScript consumes the main thread. However, with proper implementation, publishers can maintain "Good" CWV scores while running a full ad stack.
How do I prevent ads from causing CLS?
The primary technique is size reservation: set explicit CSS min-height on every ad container matching the expected ad size before any ad script loads. For multi-size slots, reserve space for the largest possible creative. Avoid dynamically injecting ad containers after page load.
Should I lazy load ads?
Yes, lazy loading below-the-fold ads is one of the most effective techniques for improving LCP and load speed. Only load ad scripts for slots within 1-2 viewports of the current scroll position. There is no viewability impact since those ads are not visible yet.
What CLS score is acceptable for pages with ads?
Google considers CLS of 0.1 or below as "Good." Many well-optimized ad-supported sites achieve 0.02-0.05. If your CLS exceeds 0.1, ads are likely a major contributor and you should implement size reservation immediately.
Ads That Do Not Slow You Down
WeForAds ad tags are built with Core Web Vitals optimization at their core: automatic size reservation, lazy loading, async everything. Maximum revenue, minimum performance impact.
See the Difference
By