DEV Community

Cover image for Implementing Google Publisher Tag Ads in Next.js 15 Single Page Application 2025
Muhammad Azfar Aslam
Muhammad Azfar Aslam

Posted on

Implementing Google Publisher Tag Ads in Next.js 15 Single Page Application 2025

Hi, Salut, Hola, Bonjour, Salam,

I’m back with another informative tutorial! Whenever I encounter a tricky problem and can’t find a proper solution or guide compiled in one place on the Internet, I take it as an opportunity to create one. This way, I contribute to our developer community and ensure that others don’t experience the same frustrations I did.

Google Publisher Tag (GPT) ads can be challenging to implement in a Single-Page Application (SPA) due to issues such as ads only loading on the first-page load, failing to reload on route changes, and layout shifts causing Cumulative Layout Shift (CLS). In this blog, I’ll share my solution for implementing GPT ads in Next.js 15 SPA, ensuring they work seamlessly on route changes while addressing CLS issues.

The Problem

  1. Ads Only Load on First Page: In SPAs, scripts often execute only on the initial load. As a result, ads might not refresh when navigating to a new route.

  2. Cumulative Layout Shift (CLS): Ads loading asynchronously often cause layout shifts, which impact the user experience and Core Web Vitals.

The Solution

After thorough research and experimentation, I came up with a solution to:

  • Ensure ads load dynamically on route changes.

  • Predefine ad sizes to avoid CLS.

Adding the GPT Script

To start, include the GPT script in the root layout file to ensure it’s available throughout the app. In rootlayout.js:

<script
  async
  src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"
></script>
<script
  dangerouslySetInnerHTML={{
    __html: `window.googletag = window.googletag || { cmd: [] };
      // Pushes a function to the GPT command queue to define and display the ad slot dynamically
      googletag.cmd.push(() => {
        googletag.pubads().enableSingleRequest();
        googletag.enableServices();
      });`,
  }}
/>
Enter fullscreen mode Exit fullscreen mode

Ad Banner Component

Next, create a reusable AdBanner.jsx component. This component ensures each ad slot has a predefined height and width, which prevents CLS:

import React from "react";
import Ad from "@/components/common/Ad";

const AdBanner = ({ width = "728px", height = "90px", adId, slot }) => {
  return (
    <div className="headerTopAdInner" style={{ width, height }}>
      <Ad adId={adId} slot={slot} />
    </div>
  );
};

export default AdBanner;
Enter fullscreen mode Exit fullscreen mode

Ad Component

The Ad component dynamically loads and renders ads. It ensures cleanup on unmount to prevent memory leaks and stale ads:

import React, { useEffect } from "react";
import { ads } from "@/utils/ads";

function Ad({ adId, slot }) {
  // This component handles dynamic ad loading by defining and displaying ad slots
  // It ensures proper cleanup on unmount to avoid memory leaks and stale ads
  // Fetching the ad configuration for the given adId from a predefined list of ads
  const ad = ads[adId];

  useEffect(() => {
    const { googletag } = window;

    // This check ensures the ad configuration exists before attempting to define the ad slot
    if (!ad) {
      console.error(`Ad configuration not found for adId: ${adId}`);
      return;
    }

    googletag.cmd.push(() => {
      console.log(`Defining slot for adId: ${adId}`);

      const existingSlot = googletag.pubads().getSlots().find(
        (slot) => slot.getSlotElementId() === adId
      );
      if (existingSlot) {
        googletag.destroySlots([existingSlot]);
      }

      const mapping = googletag
        .sizeMapping()
        .addSize([1024, 0], ad.mapping[1024] || [])
        .addSize([0, 0], ad.mapping[0] || [])
        .build();

      googletag
        .defineSlot(slot, ad.sizes, adId)
        .defineSizeMapping(mapping)
        .addService(googletag.pubads());

      googletag.display(adId);
    });

    // This return function acts as a cleanup mechanism, removing the ad slot when the component unmounts
    return () => {
      googletag.cmd.push(() => {
        const currentSlot = googletag.pubads().getSlots().find(
          (slot) => slot.getSlotElementId() === adId
        );
        if (currentSlot) {
          googletag.destroySlots([currentSlot]);
        }
      });
    };
  }, [ad, adId, slot]);

  if (!ad) {
    return <div>Error: Ad configuration not found for adId: {adId}</div>;
  }

  return <div id={adId}></div>;
}

export default Ad;
Enter fullscreen mode Exit fullscreen mode

Ad Configuration

Define all your ad configurations in a centralized file (ads.js):

export const ads = {
  top: {
    sizes: [
      [728, 90],
      [300, 250],
    ],
    mapping: {
      0: [300, 250],
      1024: [728, 90],
    },
  },
  bottom: {
    sizes: [300, 250],
    mapping: {
      0: [300, 250],
      1024: [],
    },
  },
  sidebar: {
    sizes: [
      [728, 90],
      [300, 250],
    ],
    mapping: {
      0: [300, 250],
      1024: [728, 90],
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Using AdBanner

Finally, integrate AdBanner into your components. For example, in the rootlayout.js, place ads above the header and in the footer:

<AdBanner adId="top" slot="/2233445566/someName/top" />
<AdBanner adId="bottom" slot="/2233445566/someName/bottom" />
Enter fullscreen mode Exit fullscreen mode

For article detail pages, add ads in the sidebar:

<AdBanner adId="sidebar" slot="/2233445566/someName/sidebar" />
Enter fullscreen mode Exit fullscreen mode

Replace 2233445566 and someName with your Google Ad Manager account details.

Benefits of the Solution

  1. Dynamic Ad Loading: The useEffect hook ensures ads reload dynamically on route changes.

  2. CLS Prevention: Predefined dimensions for each ad slot eliminate layout shifts.

  3. Slot Cleanup: Destroying slots on unmount prevents stale ads and memory leaks.

Conclusion

Implementing Google Publisher Tag ads in a Next.js 15 SPA requires careful handling of script initialization, dynamic loading, and slot cleanup. By following this approach, you can ensure your ads work seamlessly across route changes while maintaining a smooth user experience.

I hope this will be helpful, love to connect with you on LinkedIn


Few more articles

Top comments (0)