How To Integrate Google AdSense into Modern Web Apps (React & Next.js)

Integrating Google AdSense into modern Single Page Applications (SPAs) or server-rendered frameworks (like Next.js) requires more care than traditional static HTML. Naively injecting scripts can degrade Core Web Vitals (FID/LCP/CLS), throw duplicate initialization errors during client-side route transitions, or fail Content Security Policy (CSP) audits.

1. Setting Up ads.txt

Before placing ad tags, you must host an ads.txt file at the root of your domain (e.g., https://yourdomain.com/ads.txt) to authenticate ad inventory and prevent revenue loss from ad fraud:

Place this file in your framework's public directory (/public/ads.txt in Next.js or Vite):

google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0

Replace pub-XXXXXXXXXXXXXXXX with your actual AdSense Publisher ID. Verify that visiting https://yourdomain.com/ads.txt returns 200 OK with Content-Type: text/plain and no 301/302 redirects.

2. Loading the AdSense Script

In Next.js (next/script)

Use the afterInteractive or lazyOnload strategy to prevent AdSense from blocking your page's First Contentful Paint (FCP) and Largest Contentful Paint (LCP):

// app/layout.tsx (Next.js App Router) or pages/_app.tsx (Pages Router)
import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Script
          id="google-adsense"
          async
          src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX"
          crossOrigin="anonymous"
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

In Plain HTML

<script
  async
  src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX"
  crossorigin="anonymous"
></script>

3. Reusable React / Next.js Ad Component

In client-side SPAs, navigating between pages does not trigger a full browser reload. Standard <ins> tags will fail to load or throw adsbygoogle.push() error: No slot size for availableWidth=0 if not properly mounted.

Here is a robust, production-ready React component:

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

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

interface AdBannerProps {
  slot: string;
  format?: 'auto' | 'fluid' | 'rectangle';
  responsive?: boolean;
  style?: React.CSSProperties;
}

export const AdBanner: React.FC<AdBannerProps> = ({
  slot,
  format = 'auto',
  responsive = true,
  style,
}) => {
  const adRef = useRef<HTMLModElement>(null);
  const isLoadedRef = useRef(false);

  useEffect(() => {
    // Avoid double push in React StrictMode
    if (isLoadedRef.current) return;

    try {
      if (typeof window !== 'undefined') {
        (window.adsbygoogle = window.adsbygoogle || []).push({});
        isLoadedRef.current = true;
      }
    } catch (err) {
      console.error('AdSense push error:', err);
    }
  }, []);

  return (
    <div
      style={{
        overflow: 'hidden',
        minHeight: '100px', // Prevents Cumulative Layout Shift (CLS)
        margin: '1.5rem 0',
        textAlign: 'center',
      }}
    >
      <ins
        ref={adRef}
        className="adsbygoogle"
        style={{ display: 'block', ...style }}
        data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"
        data-ad-slot={slot}
        data-ad-format={format}
        data-full-width-responsive={responsive ? 'true' : 'false'}
      />
    </div>
  );
};

4. Preventing Cumulative Layout Shift (CLS)

When Google AdSense dynamically inserts an ad iframe, it pushes existing page content downward, severely degrading your Cumulative Layout Shift (CLS) score in Google PageSpeed Insights.

Best Practices:

  1. Reserve Minimum Height: Always wrap ad slots inside a container with a fixed minimum height matching your target ad size (e.g. min-height: 250px for desktop rectangles or min-height: 90px for leaderboards).
  2. Never Place Ads at the Very Top of Viewport: Avoid placing dynamic height banners above the primary <h1> heading where sudden shifts will push main content below the fold.

5. Content Security Policy (CSP) Requirements

If your site enforces a strict Content Security Policy via HTTP headers, you must whitelist Google AdSense domains:

Content-Security-Policy:
  script-src 'self' 'unsafe-inline' https://pagead2.googlesyndication.com https://adservice.google.com;
  frame-src https://googleads.g.doubleclick.net https://pagead2.googlesyndication.com;
  img-src 'self' data: https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net;

6. Common Troubleshooting

Issue / Error Cause Resolution
"No slot size for availableWidth=0" The parent container has display: none or zero width when push() was executed. Ensure the ad component is only rendered when visible or set explicit width on the wrapper.
Ads not displaying (Blank area) New domain, missing ads.txt, or ad blockers active. Check browser DevTools console; test with ad-blocker disabled; verify domain is "Ready" in AdSense dashboard.
"Crawler: Host unreachable" Google's AdSense crawler was blocked by firewall, Cloudflare bot management, or robots.txt. Ensure your robots.txt does not disallow the User-Agent Mediapartners-Google.
Loading...