Skip to main content

Fixing 'No slot size for availableWidth=0' Error in AdSense

 You deploy a new dynamic user interface featuring tabs, accordions, or infinite scroll, only to discover a persistent console error breaking your monetization: Uncaught TagError: adsbygoogle.push() error: No slot size for availableWidth=0.

This failure occurs silently in the background, preventing ads from rendering and directly impacting revenue. Standard responsive ad units expect a layout context that dynamic single-page applications (SPAs) often do not provide at the exact moment of execution.

To fix this, developers must decouple the ad placement from the execution trigger, ensuring the ad request only fires when the browser has successfully calculated the container's dimensions.

Understanding the Root Cause of the AdSense TagError

Google AdSense responsive ad units dynamically calculate the optimal banner size based on the width of their parent container. To do this, the inline script relies on the browser's native DOM layout properties, specifically offsetWidth and clientWidth.

When you place an ad inside a hidden container—such as a closed accordion panel, an inactive tab, or a modal window—the CSS display: none rule applies to the parent element. In the DOM layout model, elements with display: none (and their children) are removed from the render tree. Consequently, their computed width is strictly 0.

When the AdSense script executes, it queries the available space. Finding a width of 0, it aborts the render process and throws the AdSense TagError, commonly referred to as the Hidden div AdSense error.

Because the AdSense library (adsbygoogle.js) marks the <ins> tag as processed regardless of failure, simply revealing the container later will not trigger a retry. The responsive ad width zero state is permanently locked in for that specific ad unit lifecycle.

The Solution: Asynchronous Ad Initialization

To resolve the No slot size for availableWidth=0 error, you must prevent the AdSense script from executing its push({}) method until two conditions are met:

  1. The container is visible in the DOM.
  2. The container has a computed width greater than zero.

We achieve this by stripping the inline script tags provided by the default AdSense snippet and replacing them with a centralized IntersectionObserver or ResizeObserver pattern.

Step 1: Clean Up the HTML Structure

Remove the inline <script> tag that immediately follows your <ins> tag. Your ad placement should consist purely of the semantic HTML.

<!-- Do NOT include the adsbygoogle.push() script here -->
<div class="ad-container">
  <ins class="adsbygoogle"
       style="display:block"
       data-ad-client="ca-pub-1234567890123456"
       data-ad-slot="1234567890"
       data-ad-format="auto"
       data-full-width-responsive="true"></ins>
</div>

Step 2: Implement the Vanilla JS Observer

Deploy a global observer that watches your ad units. The IntersectionObserver is highly efficient for this, as it offloads visibility calculations to the browser's main thread and avoids expensive DOM thrashing.

/**
 * Initializes AdSense units only when they are visible and have a valid width.
 */
class AdSenseManager {
  constructor() {
    this.observer = new IntersectionObserver(this.handleIntersection.bind(this), {
      root: null,
      rootMargin: '200px', // Load slightly before coming into view
      threshold: 0
    });
  }

  handleIntersection(entries, observer) {
    entries.forEach((entry) => {
      // Extract the DOM node
      const adElement = entry.target;

      // Check visibility and physical width
      const hasValidWidth = entry.boundingClientRect.width > 0;
      const isVisible = entry.isIntersecting;

      if (isVisible && hasValidWidth) {
        this.triggerAdRequest(adElement);
        // Stop observing once the ad is requested
        observer.unobserve(adElement);
      }
    });
  }

  triggerAdRequest(adElement) {
    try {
      // Ensure the AdSense array exists
      window.adsbygoogle = window.adsbygoogle || [];
      window.adsbygoogle.push({});
      
      // Mark as initialized to prevent duplicate processing
      adElement.dataset.adsInitialized = "true";
    } catch (error) {
      console.error('AdSense TagError prevented:', error);
    }
  }

  observeAll() {
    // Select all uninitialized AdSense units
    const uninitializedAds = document.querySelectorAll(
      'ins.adsbygoogle:not([data-adsInitialized="true"])'
    );
    
    uninitializedAds.forEach((ad) => {
      this.observer.observe(ad);
    });
  }
}

// Initialize on DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
  const adManager = new AdSenseManager();
  adManager.observeAll();
});

Step 3: The Modern React Approach (Custom Hook)

If you are building a SPA with React, Next.js, or Vue, lifecycle mismatches are the primary cause of this error. Component mounting does not guarantee CSS painting.

Here is a robust React component utilizing useEffect and ResizeObserver to guarantee the ad is pushed only when layout dimensions are established.

import React, { useEffect, useRef, useState } from 'react';

interface AdSenseProps {
  client: string;
  slot: string;
  format?: string;
  responsive?: boolean;
}

export const AdSenseUnit: React.FC<AdSenseProps> = ({
  client,
  slot,
  format = 'auto',
  responsive = true,
}) => {
  const adRef = useRef<HTMLModElement>(null);
  const [isPushed, setIsPushed] = useState(false);

  useEffect(() => {
    const currentAd = adRef.current;
    if (!currentAd || isPushed) return;

    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        if (entry.contentRect.width > 0) {
          try {
            (window.adsbygoogle = window.adsbygoogle || []).push({});
            setIsPushed(true);
            resizeObserver.disconnect();
          } catch (error) {
            console.error('Failed to push AdSense unit:', error);
          }
        }
      }
    });

    resizeObserver.observe(currentAd);

    return () => {
      resizeObserver.disconnect();
    };
  }, [isPushed]);

  return (
    <div style={{ width: '100%', minHeight: '100px' }}>
      <ins
        ref={adRef}
        className="adsbygoogle"
        style={{ display: 'block' }}
        data-ad-client={client}
        data-ad-slot={slot}
        data-ad-format={format}
        data-full-width-responsive={responsive ? 'true' : 'false'}
      />
    </div>
  );
};

declare global {
  interface Window {
    adsbygoogle: any[];
  }
}

Deep Dive: Why This Architecture Works

Layout Thrashing vs. Asynchronous Layout Checking

The standard AdSense snippet executes synchronously during the HTML parser's execution or immediately upon DOM insertion. At this microsecond, the CSS Object Model (CSSOM) may not have fully resolved the display state of parent containers (e.g., waiting for external stylesheets or SPA state hydration).

By delegating the check to ResizeObserver or IntersectionObserver, you tie the ad execution to the browser's actual paint lifecycle. entry.boundingClientRect.width > 0 acts as a deterministic boolean flag proving that the CSSOM and DOM have reconciled, the element is not display: none, and AdSense has the necessary mathematical context to serve a properly sized iframe.

Decoupling the Push Array

The global adsbygoogle.push({}) array acts as a message queue for the adsbygoogle.js library. It does not map 1:1 to DOM elements by ID. Instead, when push({}) is called, the library scans the DOM for the first <ins class="adsbygoogle"> tag that lacks a data-adsbygoogle-status attribute. Ensuring the DOM element is visually ready before adding to this queue ensures the library always finds a valid container.

Common Pitfalls and Edge Cases

CSS Visibility vs Display

Developers often confuse visibility: hidden with display: none. Elements with visibility: hidden still occupy space in the DOM render tree, meaning their width is strictly calculated. AdSense will successfully inject an ad into a visibility: hidden container, but the ad will be invisible to the user. This triggers severe AdSense policy violations for "hidden ads." Always use display: none for tabs and accordions, combined with the observer pattern above.

Single Page Application (SPA) Ad Limits

Google AdSense limits the total number of unfulfilled push({}) requests. In an SPA (like React or Angular), navigating between pages without a hard refresh can leave orphan <ins> tags in memory. If you unmount an ad component before the observer fires, ensure you disconnect the observer (as demonstrated in the React useEffect cleanup function) to prevent memory leaks and zombie push() calls that will result in a TagError on the next route.

Fixed-Size Fallbacks

If your layout strictly prevents dynamic width calculation before rendering, bypass the responsive ad unit entirely. Modify the <ins> tag to use explicit CSS dimensions. When width and height are strictly defined inline, AdSense skips the availableWidth calculation algorithm entirely.

<ins class="adsbygoogle"
     style="display:inline-block;width:300px;height:250px"
     data-ad-client="ca-pub-1234567890123456"
     data-ad-slot="1234567890"></ins>

By enforcing a deferred execution architecture based on browser-native observer APIs, developers guarantee optimal fill rates, eliminate rendering errors, and maintain strict adherence to programmatic ad lifecycle requirements.