Publisher Ad Monetization

What Is Lazy Loading for Ads and How Does It Affect Revenue?

Last updated: August 2, 2026 · 8 min read

Lazy loading for ads defers fetching and rendering ad creative until the user scrolls within a predefined distance of the ad slot's viewport position. Instead of loading all ads on page open, you load them on demand. This improves page speed and viewability scores while introducing a trade-off on total impression volume. This article covers the technical mechanics, optimal fetch distances, Core Web Vitals impact, and how to implement lazy loading in Google Ad Manager and Prebid.js without losing impressions.

How Does Lazy Loading for Ads Work?

Lazy loading uses the IntersectionObserver API to detect when an ad slot approaches the visible viewport, then triggers the ad request. The ad renders before the user scrolls to it, so the experience feels instant. No ad loads until the user demonstrates scroll intent.

The IntersectionObserver watches each ad slot's DOM element. You define a rootMargin value (the fetch distance below the viewport) that determines how early the trigger fires. When the slot crosses that threshold, the observer callback executes your ad request code.

Legacy implementations used window.scroll event listeners that fire on every scroll pixel, consuming CPU. IntersectionObserver is asynchronous and hardware-accelerated, running off the main thread with effectively zero performance cost.

Without lazy loading (eager loading), every ad slot fires its request on page open. Above-fold, below-fold, footer. All load simultaneously, front-loading bandwidth, delaying LCP, and generating impressions for ads users may never see. Those non-viewable impressions drag your viewability score down and trigger DSP bid shading.

How Does Lazy Loading Affect Ad Viewability and CPM?

Lazy loading increases viewability because every loaded ad is near or inside the viewport when it renders. Ads that would have loaded off-screen under eager loading, counted as served but unviewable, never fire until they're relevant.

The viewability lift is measurable. Publishers implementing lazy loading typically see Active View scores increase 10 to 15 percentage points. A site moving from 50% to 65% viewability enters the tier where DSPs bid 20 to 40% higher CPMs because their models show more impressions will actually be seen.

The trade-off: total impression volume may drop slightly. Under eager loading, a below-fold ad slot generates an impression whether the user scrolls to it or not. Under lazy loading, if the user bounces before scrolling, that slot never loads and no impression is counted. You lose impressions that were never going to be seen anyway.

For most sites, the CPM gain from higher viewability exceeds the revenue lost from unserved non-viewable impressions. The lost impressions had near-zero viewability and attracted shaded bids. Removing them raises your domain viewability average, which raises bids on every remaining impression.

Model the impact using the eCPM calculator: compare current eCPM at your current viewability rate against projected eCPM at a 10 to 15 point viewability increase, adjusted for 5 to 10% fewer total impressions.

What Fetch Distance Should You Set for Lazy Loading?

The rootMargin value determines how far below the viewport the ad request fires. Too large and you're essentially eager loading again. Too small and the ad hasn't finished rendering by the time the user scrolls to it, causing a blank slot flash or a Cumulative Layout Shift (CLS) penalty.

  • Desktop (broadband): 200 to 300px. Broadband connections fetch and render ads in 100 to 200ms. A 200px margin gives the ad server roughly 500ms of scroll time to complete the request at average desktop scroll speed. Sufficient for most ad stacks.
  • Mobile (variable connections): 300 to 500px. Mobile networks add 50 to 150ms of additional latency per request. A 300px margin is the minimum. On pages targeting users in regions with slower mobile infrastructure, 400 to 500px is safer.
The 0px mistake. Setting rootMargin to 0px means the ad request fires only when the slot enters the viewport. The user already sees a blank space. The ad takes 200 to 400ms to render. Content jumps as the ad loads, spiking CLS and missing impressions from fast scrollers.

Test your fetch distance by throttling your connection to 3G in Chrome DevTools and scrolling at a normal pace. If you see blank ad slots before the creative renders, increase your rootMargin by 100px and retest.

How Does Lazy Loading Improve Core Web Vitals?

Lazy loading directly improves two of Google's three Core Web Vitals metrics: Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS). It can also reduce Interaction to Next Paint (INP) by keeping the main thread lighter during page load.

  • LCP improvement: By deferring below-fold ad requests, you remove their scripts and creative payloads from the initial page load. This lets LCP-eligible elements (hero images, heading text) render faster. Typical LCP improvements: 200 to 600ms reduction depending on how many ad slots you defer.
  • CLS protection: Reserve the ad slot's dimensions in the DOM with explicit width/height or CSS min-height before the ad loads. The slot holds its space. The ad fills it without pushing content. Skip dimension reservation and lazy loading worsens CLS because the slot collapses to 0px and expands on render.
  • INP improvement: Fewer concurrent scripts during initial load means fewer main-thread tasks competing for processing time. Users clicking navigation elements or interactive content during the first few seconds get faster responses.

Google Lighthouse scores these metrics. Sites passing all three CWV thresholds receive a ranking signal in Google Search. For publishers within publisher ad monetization, failing CWV due to ad loading directly threatens the organic traffic that generates ad revenue.

How Do You Implement Lazy Loading in GAM and Prebid.js?

Google Ad Manager: GAM doesn't natively lazy load ad slots. You control the timing of googletag.display() calls in your JavaScript. Instead of calling display() on page load for every slot, wrap each below-fold slot's display call inside an IntersectionObserver callback with your chosen rootMargin.

Reserve the ad slot dimensions in your HTML or CSS before the observer fires. When the observer triggers, call googletag.display(slotId) followed by googletag.pubads().refresh([slot]) to fetch and render the ad.

Prebid.js: Prebid's requestBids() function fires header bidding auctions. For lazy-loaded slots, defer requestBids() until the IntersectionObserver triggers, then pass the returned bids to GAM via pbjs.setTargetingForGPTAsync() before calling googletag.display().

The timing sequence matters: IntersectionObserver fires > Prebid requests bids (100 to 300ms) > bids returned > GAM request fires with targeting > creative renders. Total: 200 to 500ms. Your rootMargin must account for this full chain. If Prebid adds 200ms and GAM adds 150ms, a 300px mobile rootMargin may need to increase to 400 to 500px.

Managed networks (Mediavine, Raptive): Both handle lazy loading internally. You don't configure it manually. Their implementations use optimized fetch distances calibrated for their demand partners. Once ads are lazy loaded into the viewport, you can layer ad refresh on top for additional impressions.

Check your implementation's impact on fill rate using GAM's unfilled impressions report. If unfilled impressions increase after deploying lazy loading, your rootMargin is too small and ads aren't rendering in time.

Use the CPM calculator to compare total revenue before and after deployment: (old impression count x old eCPM) vs (new impression count x new eCPM). If the new total exceeds the old despite fewer impressions, lazy loading is working as intended.

Frequently Asked Questions About Lazy Loading Ads

How does lazy loading ads affect overall ad revenue?

Lazy loading increases overall ad revenue over time by filtering out unviewable ad requests, boosting domain viewability scores to 70%+, which attracts higher DSP bids and increases long-term CPMs.

Does Google Ad Manager support lazy loading for ad units?

Yes, Google Ad Manager natively supports lazy loading via the Google Publisher Tag (GPT) API using the enableLazyLoad method, allowing publishers to configure fetch and render margin thresholds.

What is the optimal margin distance for lazy loading ads?

Industry best practice is to set render margins around 200px for desktop and 300px to 500px for mobile devices to accommodate network latency and fast user scrolling.

Does lazy loading ads improve website Core Web Vitals?

Yes, deferring ad scripts reduces initial DOM payload size and main-thread blocking time, significantly improving Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS) scores.

Can lazy loading cause lower ad fill rates or lost impressions?

If fetch margins are configured too tightly (e.g., 0px), users may scroll past ad slots before creatives finish loading, leading to unrendered ad calls and lower realized fill rates.