Every ad slot on your page costs something. Even if the user never scrolls far enough to see it, the browser still fires the ad request, downloads the creative, and executes the rendering JavaScript. On a page with 6 ad slots, the 3 below-fold units that most users never reach are consuming bandwidth, blocking the main thread, and dragging down your Core Web Vitals scores for zero viewable impressions.

Lazy loading ads solves this by deferring ad requests and rendering until the slot is about to enter the viewport. The result: faster initial page loads, better CWV scores, lower bandwidth costs, and higher viewability rates on the impressions that do fire. This guide covers exactly how it works, how to configure it in Google Publisher Tag (GPT), the revenue trade-offs to be aware of, and the mistakes that cost publishers money.

What Is Lazy Loading for Ads?

Lazy loading is a technique where ad slots below the viewport are not requested from the ad server until the user scrolls close to them. Instead of requesting all ad slots the moment the page loads (eager loading), lazy loading defers the fetch-render cycle for each slot until it is within a configurable distance from the visible area.

The process works in three stages:

  1. Slot definition: The ad slot is defined in the page's HTML and JavaScript as usual, but no ad request is sent yet. The slot container exists in the DOM with reserved dimensions, but it is empty.
  2. Fetch trigger: When the user scrolls and the slot enters the "fetch margin" (a configurable distance from the viewport, measured in viewport heights), the browser sends the ad request to the ad server. This is when the auction happens, bids are collected, and a winning creative is selected.
  3. Render trigger: When the slot enters the "render margin" (a smaller distance, closer to the viewport), the winning creative is rendered into the slot's iframe. The user sees the ad appear as they scroll to it.

The two-stage approach (fetch then render) is important. Ad requests take time to complete, especially with header bidding. By fetching well before the slot is visible and rendering only when it is close, you avoid the user seeing an empty slot that fills in with a visible pop.

Why Lazy Load Ads?

There are four concrete reasons to lazy load below-fold ad slots, and they all compound.

1. Faster Initial Page Load

Ad tags are heavy. A single GPT ad request triggers a chain of network calls: the initial request to Google's ad server, header bidding auctions (if you use Prebid or similar), creative download (which may be a 200KB rich media unit), and rendering JavaScript execution. Multiply that by 5-6 ad slots all firing on page load, and you have a significant amount of network and CPU work competing with your actual page content.

Lazy loading removes below-fold ad requests from the critical rendering path. On a typical article page with 6 ad slots where 3 are below the fold, lazy loading those 3 slots reduces initial ad-related network requests by 50% and frees up main thread time for content rendering.

2. Better Core Web Vitals

Google uses Core Web Vitals as a ranking signal. Ad-heavy pages consistently struggle with LCP (Largest Contentful Paint) and INP (Interaction to Next Paint) because ad scripts compete with content resources. Lazy loading directly improves both metrics by deferring the heaviest JavaScript execution until after the initial page render is complete.

Publishers who implement lazy loading for below-fold ads typically see LCP improvements of 200-800ms depending on connection speed and the number of deferred slots. On mobile connections (where Google measures CWV for ranking purposes), the improvement is even more pronounced.

3. Higher Viewability Rates

When you eagerly load all ad slots, you pay for impressions on slots that users never scroll to. These unviewable impressions drag down your overall viewability rate, which in turn lowers the CPMs advertisers are willing to pay across your inventory. A site with 45% viewability gets significantly lower bids than the same site at 65% viewability.

Lazy loading naturally eliminates unviewable impressions because ads are only requested when the user is likely to see them. This raises your aggregate viewability rate without any other changes, which improves CPMs on all your inventory through the viewability halo effect in programmatic auctions.

4. Reduced Bandwidth and Server Costs

Every ad request that fires consumes user bandwidth and generates server-side costs (bid requests to SSPs, creative hosting bandwidth, tracking pixels). For the 20-40% of below-fold impressions that are never viewed, this is pure waste. Lazy loading eliminates it. On pages with high bounce rates or short average scroll depths, the savings are substantial.

How GPT Lazy Loading Works

Google Publisher Tag (GPT) has built-in lazy loading support through the enableLazyLoad() method. This is the standard implementation for publishers using Google Ad Manager.

The Three Parameters

GPT's lazy loading is controlled by three parameters that define when ad slots are fetched and rendered relative to the viewport:

fetchMarginPercent — The distance from the viewport (as a percentage of viewport height) at which the ad request is sent. A value of 500 means the fetch triggers when the slot is within 5 viewport heights of the visible area. Default: 500 (500%).

renderMarginPercent — The distance from the viewport at which the creative is rendered into the slot. A value of 200 means rendering occurs when the slot is within 2 viewport heights. This should always be smaller than fetchMarginPercent to ensure the ad response has arrived before the render trigger fires. Default: 200 (200%).

mobileScaling — A multiplier applied to both margins on mobile devices. Because users scroll faster on mobile (touch-based flick gestures cover more distance than mouse wheel scrolling), the margins need to be larger to ensure ads load in time. A value of 2.0 doubles the margins on mobile. Default: 2.0.

Configuration Example

The basic implementation looks like this:

googletag.cmd.push(function() {
  googletag.pubads().enableLazyLoad({
    fetchMarginPercent: 500,  // Fetch 5 viewports away
    renderMarginPercent: 200, // Render 2 viewports away
    mobileScaling: 2.0        // Double margins on mobile
  });
});

This configuration means on desktop, an ad slot 5 viewport-heights below the current scroll position will have its request sent, and it will be rendered when it is 2 viewport-heights away. On mobile, those distances double to 10 and 4 viewport-heights respectively (because mobileScaling is 2.0).

How the Margins Interact

Think of the margins as two concentric zones around the viewport. The outer zone (fetchMarginPercent) triggers the ad request. As the user continues scrolling, the slot enters the inner zone (renderMarginPercent), and the creative renders. The gap between these two zones is the "buffer time" for the ad auction to complete.

If the user scrolls very fast and the slot jumps from outside the fetch margin directly into the render margin, GPT will fire both the fetch and render simultaneously. This means the ad renders as soon as the response arrives, but there may be a visible delay where the slot appears empty. This is why generous margins matter, especially on mobile.

Margin Math

On a mobile device with a 700px viewport height, fetchMarginPercent of 500, and mobileScaling of 2.0: the effective fetch distance is 700 x 5.0 x 2.0 = 7,000px below the viewport. For an average article with 4,000-6,000px of total scroll depth, this means most ad slots will be fetched almost immediately after page load anyway. The real performance gains come on very long pages (10,000px+) or when you reduce margins to more aggressive values like fetchMarginPercent: 200.

Applying Lazy Loading Selectively

GPT's enableLazyLoad() applies globally to all slots by default. If you need to exclude specific slots (like above-the-fold units), you can control this at the slot level using googletag.Slot.setForceSafeFrame() or by calling googletag.pubads().refresh([slot]) manually for slots you want to load immediately.

A common pattern is to eagerly load above-fold slots in the initial googletag.pubads().refresh() call, then let lazy loading handle the rest:

googletag.cmd.push(function() {
  // Enable lazy loading globally
  googletag.pubads().enableLazyLoad({
    fetchMarginPercent: 500,
    renderMarginPercent: 200,
    mobileScaling: 2.0
  });

  // Define all slots
  var topSlot = googletag.defineSlot('/1234/top-leaderboard', [728, 90], 'div-top');
  var midSlot = googletag.defineSlot('/1234/mid-article', [300, 250], 'div-mid');
  var bottomSlot = googletag.defineSlot('/1234/bottom-banner', [728, 90], 'div-bottom');

  googletag.pubads().enableSingleRequest();
  googletag.enableServices();

  // Immediately display above-fold slot
  googletag.display('div-top');

  // Below-fold slots will be fetched/rendered by lazy loading
  googletag.display('div-mid');
  googletag.display('div-bottom');
});

Note: With Single Request Architecture (SRA) enabled, GPT batches ad requests. Lazy loading still works, but the batching behavior means that when the first lazy slot triggers, all remaining unfetched slots within the fetch margin may be included in the same request. This is generally fine and actually improves auction efficiency through competitive exclusion.

Lazy Loading vs. Eager Loading: The Revenue Trade-Off

The core tension with lazy loading is between page performance and raw impression volume. Understanding this trade-off is critical to configuring margins correctly.

What You Gain

What You Risk

The Net Revenue Effect

In practice, most publishers see a neutral to slightly positive revenue impact from lazy loading when configured correctly. The math works like this: you lose 10-20% of below-fold impressions (the ones that were never viewable anyway), but the remaining impressions earn 10-20% higher CPMs due to improved viewability. The result is roughly the same revenue with significantly better page performance.

Where lazy loading clearly wins on revenue is the second-order effect: better Core Web Vitals lead to better search rankings, which lead to more organic traffic, which generates more total ad impressions. A page that loads 500ms faster and ranks 2-3 positions higher in search results will generate substantially more total revenue than the same page with marginally higher per-pageview RPM.

Best Practices for Lazy Loading Ads

Set Fetch Margins Generously

The fetch margin is the most important parameter. Set it too low and ads arrive late, creating blank slots and layout shifts. Set it too high and you lose the performance benefits of lazy loading entirely. A fetchMarginPercent of 400-600 (4-6 viewport heights) works well for most sites. This gives the ad server 2-4 seconds of scroll time to return a creative before the slot enters the render zone.

Keep Render Margins Moderate

The render margin controls when the creative is actually painted into the DOM. A renderMarginPercent of 150-250 (1.5-2.5 viewport heights) ensures the ad is rendered before the user sees the slot, without rendering so early that it negates the performance benefit. The key constraint is: renderMarginPercent must be meaningfully smaller than fetchMarginPercent to allow time for the ad response.

Never Lazy Load Above-the-Fold Slots

The top leaderboard, the first in-content unit, and any ad visible without scrolling must load eagerly. Lazy loading these slots delays their render, which means:

Never Lazy Load Sticky and Interstitial Ads

Sticky ads (footer bars, sidebar sticky units) and interstitials are triggered by user actions or scroll position, not by their DOM position on the page. Lazy loading them can cause timing conflicts where the trigger fires before the ad is loaded. Always eagerly load sticky and interstitial slots, then control their visibility with JavaScript based on scroll or engagement triggers.

Reserve Slot Dimensions in CSS

Even with lazy loading, the ad container should have explicit dimensions set via CSS min-height and min-width. This prevents layout shift when the ad renders. Without reserved dimensions, the slot container is 0px tall until the creative loads, then suddenly expands, pushing content down and causing a CLS spike.

.ad-slot-mid-article {
  min-height: 250px;
  min-width: 300px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f5f5; /* Optional placeholder background */
}

Adjust Mobile Scaling Based on Your Audience

The default mobileScaling of 2.0 works for most sites, but if your audience includes a high proportion of users on slow mobile connections (3G or rural areas), consider increasing it to 2.5 or 3.0. Slower connections mean ad responses take longer to return, so the fetch margin needs to be larger. Conversely, if your audience is primarily on fast connections (urban, desktop-heavy), you can reduce mobileScaling to 1.5 without issues.

Test with Real User Monitoring

Do not rely solely on lab testing (Lighthouse, PageSpeed Insights) to evaluate lazy loading impact. Lab tests use synthetic scrolling that does not match real user behavior. Use your ad server's reporting to compare viewability rates, fill rates, and CPMs before and after enabling lazy loading. Run an A/B test for 2-4 weeks if your traffic volume supports it.

Impact on Core Web Vitals

Lazy loading affects all three Core Web Vitals, mostly positively, but with caveats you need to understand.

Largest Contentful Paint (LCP)

LCP measures how quickly the largest visible content element renders. Ad scripts compete with content resources for bandwidth and main thread time during initial page load. By deferring below-fold ad requests, lazy loading frees up network connections and CPU cycles for content resources, directly improving LCP.

Typical improvement: 200-800ms, depending on how many ad slots are deferred and the user's connection speed. On 4G mobile connections, the improvement tends to be 400-600ms because ad scripts are proportionally heavier relative to available bandwidth.

Caveat: If your LCP element is an ad (rare, but it happens on pages where a large display ad is above the fold), lazy loading that specific slot would make LCP worse. Always eagerly load slots that could be the LCP element.

Cumulative Layout Shift (CLS)

CLS measures unexpected layout movement. Ads are one of the biggest CLS offenders because they inject content into the page after layout has already been calculated. Lazy loading can both help and hurt CLS:

How it helps: Below-fold ads that load after the initial layout calculation do not contribute to the initial CLS score if the user has not scrolled to them yet. CLS is measured across the entire page session, but shifts in areas the user has not reached yet have lower impact under the windowed CLS calculation that Google uses.

How it hurts: If a lazy-loaded ad renders while the user is actively reading content near that slot, the sudden size change creates a layout shift. This is worse than eagerly loading the ad before the user reaches that area. The fix is to always reserve space for the ad container with CSS dimensions, so the slot occupies its full space before the creative loads.

Net effect: Positive, if you reserve slot dimensions. Negative, if you do not.

Interaction to Next Paint (INP)

INP measures input responsiveness. Ad JavaScript (especially rich media creatives with animation, video, or interactive elements) can block the main thread and delay input processing. Lazy loading improves INP by reducing the amount of ad JavaScript executing during the user's initial interaction window.

Typical improvement: 30-100ms reduction in worst-case INP. The improvement is most significant on pages with video or rich media ad formats, where creative JavaScript is particularly heavy.

Caveat: When multiple lazy-loaded ads trigger simultaneously (the user scrolls quickly through a section with several slots), the burst of concurrent ad JavaScript can temporarily degrade INP. Staggering ad requests with slightly different margins for adjacent slots can mitigate this.

Common Mistakes

These are the lazy loading mistakes we see most frequently in publisher implementations.

1. Lazy Loading Everything

The most common mistake is enabling lazy loading globally without excluding above-fold slots. This delays your most valuable impressions (above-fold has the highest viewability and CPMs by default), hurts LCP, and creates a visible flash of empty ad containers at the top of the page. Always exclude above-fold and sticky slots from lazy loading.

2. Setting Margins Too Aggressively

Some publishers set fetchMarginPercent to 50 or 100, thinking smaller margins will maximize performance gains. In practice, this means ads are requested when the slot is less than one viewport height away. On a 3G connection with header bidding, the ad response can take 3-5 seconds. By the time it arrives, the user has scrolled past the slot. The result is wasted impressions (the ad rendered but was never viewed) and a poor user experience (visible layout shifts as ads pop in late).

3. Not Reserving Slot Dimensions

Without explicit CSS dimensions on the ad container, the slot collapses to 0px when empty. When the lazy-loaded ad arrives and renders, it expands the container, pushing all content below it downward. This causes layout shifts that directly hurt your CLS score. Always set min-height on ad containers to match the expected creative size.

4. Ignoring Header Bidding Latency

If you run Prebid.js or another header bidding wrapper, the fetch margin must account for the additional auction time. A standard GPT ad request takes 200-500ms. A Prebid auction adds 1,000-3,000ms on top of that. Your fetchMarginPercent needs to be large enough that the total time (scroll distance / scroll speed) exceeds the combined auction latency. For most sites with header bidding, fetchMarginPercent below 300 will cause late ad loads.

5. Not Testing on Mobile

Mobile users scroll differently than desktop users. Touch-based flick gestures cover 2-5x more distance per second than mouse wheel scrolling. If you test lazy loading only on desktop and use margins that work there, mobile users will frequently outpace your fetch margins, seeing empty slots or late-rendering ads. Always test on real mobile devices (not just Chrome DevTools device emulation, which does not accurately simulate mobile scroll physics).

6. Lazy Loading Companion Ads

Companion creatives (multiple ad sizes that need to appear together, typically in a roadblock or high-impact skin) must be loaded as a group. Lazy loading individual companion slots independently can result in mismatched creatives where one companion loads and the other does not. If you serve companion ads, group them in a single SRA request and exclude them from lazy loading.

7. No Fallback for No-Scroll Users

A meaningful percentage of users (10-20% on most sites) land on a page and leave without scrolling. If all your ad slots except the top one are lazy loaded, these users generate only one impression per pageview. While this is technically correct behavior (they would not have seen below-fold ads anyway), it can significantly reduce total impressions. Consider loading the first 2-3 ad slots eagerly and only lazy loading slots that require substantial scrolling to reach.

Frequently Asked Questions

Does lazy loading ads reduce ad revenue?

Lazy loading can reduce raw impression counts by 10-20% for below-fold slots, but the remaining impressions have significantly higher viewability (70%+ vs 50%). Higher viewability attracts higher CPMs, so most publishers see neutral to slightly positive revenue impact. The real win is the second-order effect: better page speed leads to better search rankings and more organic traffic.

What are the best fetchMarginPercent and renderMarginPercent values for GPT?

For most sites, fetchMarginPercent of 500 and renderMarginPercent of 200 is the optimal starting point. The fetch margin should always be larger than the render margin to give the ad auction time to complete. On mobile, GPT's mobileScaling (default 2.0) automatically doubles these values. If you run header bidding, consider increasing fetchMarginPercent to 600 to account for the additional auction latency.

Should I lazy load all ad slots on my page?

No. Above-the-fold slots, sticky/anchor ads, interstitials, and companion ads should always be eagerly loaded. Only lazy load slots that require scrolling to reach. A typical setup eagerly loads the top leaderboard and first in-content unit, while lazy loading mid-article and bottom-of-page slots.

Does lazy loading ads improve Core Web Vitals scores?

Yes, when implemented correctly. LCP improves by 200-800ms because fewer ad requests compete with content during initial load. CLS improves if you reserve slot dimensions with CSS min-height. INP improves because less ad JavaScript executes during the initial interaction window. The critical requirement is reserving space for lazy-loaded slots to prevent layout shift.

How does lazy loading work with header bidding and Prebid.js?

Lazy loading integrates with header bidding by deferring the auction call until the slot approaches the viewport. Use an Intersection Observer to trigger the Prebid auction when the slot enters your fetch margin, then call googletag.pubads().refresh([slot]) after bids return. Your fetch margin must be large enough (500%+) to account for the 1-3 second auction time on top of the standard ad server response.

Lazy Loading Built Into Every Tag

WeForAds ad tags include intelligent lazy loading with optimized fetch and render margins, automatic above-fold detection, and CLS-safe slot reservations. No manual configuration needed.

Get Started Free
Weekly newsletter

Get these insights in your inbox

One tactical ad monetization tip per week. No fluff. Unsubscribe anytime.

Free forever. No spam. Learn more