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
Option A: Static Ad Container (Recommended)
// 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
-
Server-Side Considerations
- Use
'use client'
directive for ad components - Never load GAM scripts during SSR
- Use
-
Ad Layout Stability
- Always specify exact dimensions
- Use CSS
min-height
to reserve space - Implement skeleton loaders
-
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') })
-
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:
- Verify your ad unit path is correct
- Check for ad blockers in development
- Ensure GAM script is properly loaded
Problem: Layout shifts
Solution:
- Always reserve exact space with CSS
- Use
aspect-ratio
for responsive ads - Implement IntersectionObserver for lazy loading
Problem: Ads breaking hydration
Solution:
- Wrap ad components in
'use client'
- Only initialize GAM in
useEffect
- 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