Next.js Google Ad Manager Integration: A Complete Guide for Ad Placements

May 31, 2025

Next.js Google Ad Manager Integration: A Complete Guide for Ad Placements

Introduction: Why Proper Ad Integration Matters

In modern Next.js applications, implementing Google Ad Manager (GAM) requires special consideration for:

  • SSR/SSG compatibility - Ads must work with server-side rendering
  • Performance optimization - Ads shouldn't block page rendering
  • Layout stability - Prevent CLS (Cumulative Layout Shift)

Step 1: Create Reserved Ad Spaces in Your Layout

// components/AdPlaceholder.tsx
'use client'

import { useEffect } from 'react'

export default function AdPlaceholder({ 
  id, 
  width = 300, 
  height = 250 
}: {
  id: string
  width?: number
  height?: number
}) {
  useEffect(() => {
    // Load GAM script only on client-side
    if (typeof window !== 'undefined') {
      ;(window as any).googletag = (window as any).googletag || {}
      ;(window as any).googletag.cmd = (window as any).googletag.cmd || []
      
      const script = document.createElement('script')
      script.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'
      script.async = true
      document.head.appendChild(script)
    }
  }, [])

  return (
    <div 
      id={id}
      style={{
        width: `${width}px`,
        height: `${height}px`,
        margin: '1rem auto',
        backgroundColor: '#f5f5f5',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
      }}
    >
      {/* Fallback content */}
      <span>Advertisement</span>
    </div>
  )
}

Option B: Dynamic Ad Slot (For Multiple Ads)

// utils/loadAds.ts
declare global {
  interface Window {
    googletag: any
  }
}

export function defineAdSlot(adUnitPath: string, id: string, sizes: [number, number][]) {
  if (typeof window === 'undefined') return

  window.googletag = window.googletag || { cmd: [] }
  
  window.googletag.cmd.push(() => {
    window.googletag
      .defineSlot(adUnitPath, sizes, id)
      .addService(window.googletag.pubads())
    
    window.googletag.enableServices()
  })
}

Step 2: Implement in Next.js Page

// app/page.tsx
import { useEffect } from 'react'
import AdPlaceholder from '@/components/AdPlaceholder'

export default function Home() {
  useEffect(() => {
    // Initialize ad after component mounts
    if (typeof window !== 'undefined') {
      window.googletag = window.googletag || { cmd: [] }
      
      window.googletag.cmd.push(() => {
        window.googletag
          .defineSlot('/1234567/sports', [300, 250], 'banner-ad')
          .addService(window.googletag.pubads())
        
        window.googletag.enableServices()
        window.googletag.display('banner-ad')
      })
    }
  }, [])

  return (
    <main>
      <h1>Welcome to Our Site</h1>
      
      {/* Above-the-fold ad */}
      <AdPlaceholder id="banner-ad" width={300} height={250} />
      
      <article>{/* Page content */}</article>
      
      {/* Below content ad */}
      <AdPlaceholder id="footer-ad" width={728} height={90} />
    </main>
  )
}

Step 3: Performance Optimization Techniques

1. Lazy Loading Ads

'use client'

import { useEffect } from 'react'
import { useIntersectionObserver } from '@react-hookz/web'

export function LazyAd({ id }: { id: string }) {
  const [ref, entry] = useIntersectionObserver()
  
  useEffect(() => {
    if (entry?.isIntersecting) {
      // Load ad when component is visible
      window.googletag.cmd.push(() => {
        window.googletag.display(id)
      })
    }
  }, [entry, id])

  return <div ref={ref} id={id} />
}

2. CLS Prevention

/* styles.css */
.ad-container {
  min-height: 250px; /* Reserve space for 300x250 ad */
  width: 300px;
  margin: 0 auto;
}

3. Ad Refresh Control

useEffect(() => {
  const refreshInterval = setInterval(() => {
    if (typeof window !== 'undefined' && window.googletag) {
      window.googletag.pubads().refresh([slot]);
    }
  }, 30000); // Refresh every 30 seconds

  return () => clearInterval(refreshInterval);
}, []);

Step 4: TypeScript Support (Optional)

// types/google.d.ts
interface Googletag {
  cmd: Array<() => void>
  pubads: () => {
    (): void
    enableServices: () => void
    refresh: (slots?: any[]) => void
  }
  defineSlot: (
    adUnitPath: string,
    size: [number, number][],
    divId: string
  ) => {
    addService: (service: any) => void
  }
}

declare global {
  interface Window {
    googletag: Googletag
  }
}

Best Practices

  1. Server-Side Considerations

    • Use 'use client' directive for ad components
    • Never load GAM scripts during SSR
  2. Ad Layout Stability

    • Always specify exact dimensions
    • Use CSS min-height to reserve space
    • Implement skeleton loaders
  3. Performance Monitoring

    // Track ad loading time
    performance.mark('ad-start')
    window.googletag.cmd.push(() => {
      performance.mark('ad-end')
      performance.measure('ad-load', 'ad-start', 'ad-end')
    })
    
  4. Error Handling

    useEffect(() => {
      try {
        // Ad loading logic
      } catch (error) {
        console.error('Ad failed to load:', error)
        // Show fallback content
      }
    }, [])
    

Troubleshooting Common Issues

Problem: Ads not showing in production
Solution:

  1. Verify your ad unit path is correct
  2. Check for ad blockers in development
  3. Ensure GAM script is properly loaded

Problem: Layout shifts
Solution:

  1. Always reserve exact space with CSS
  2. Use aspect-ratio for responsive ads
  3. Implement IntersectionObserver for lazy loading

Problem: Ads breaking hydration
Solution:

  1. Wrap ad components in 'use client'
  2. Only initialize GAM in useEffect
  3. Use dynamic imports if needed

Conclusion

This implementation provides:
✅ Proper SSR/SSG compatibility
✅ Zero layout shift (CLS = 0)
✅ Optimal loading performance
✅ Type-safe integration

For advanced implementations, consider:

  • A/B testing different ad placements
  • Dynamic ad sizing based on container
  • Header bidding integration

Remember to test across:

  • Different devices
  • Various network conditions
  • With/without ad blockers

© 2025 Tech Me 365