Google Analytics with Next.Js, window.gtag is not a function

267 Views Asked by At

As the title mentions I get the following error on loading the page

window.gtag is not a function

I use Next.js 14.0.4

all solutions I have seen so far basically make data collection impossible, meaning the website I built does not set the cookie consent correctly. At this point I am unsure on how to correctly proceed. As I will show in the code, there are multiple data types I want to collect. So far with only one it worked but now it started to throw errors. I also had more errors since upgrading to next14

I used the packages @types/gtag.js and client-only for this.

my layout.tsx looks as follows:

        <html lang='en'>
            <head>
                <GoogleAnalytics />
            </head>
            <body className='overflow-x-hidden mx-auto w-[100vw] flex flex-col items-center justify-center box-border'>
                <Toaster />
                <Navbar />
                <div className="right-10 md:right-0 -top-10 md:top-10 absolute w-[100vw] h-[230px] gradient-02 -z-50 hidden sm:block" />
                <main className='w-[100vw] overflow-x-hidden mx-auto'>
                    {children}
                </main>
                <Footer />
                <CookieBanner />
            </body>
        </html>

and this is my CookieBanner Component:

'use client';
/**
 * Type Definition
 */
type ConsentState = {
    adStorage: boolean
    analyticsStorage: boolean
}

export default function CookieBanner() {

    // Define useState
    const [cookieConsent, setCookieConsent] = useState<ConsentState>();

    // useEffect checks if cookie_consent is already set
    useEffect(() => {
        const storedCookieConsent = getLocalStorage("cookie_consent", null);
        setCookieConsent(storedCookieConsent);
    }, [setCookieConsent]);

    // useEffect updates once cookieConsent is updated to update the consent values
    useEffect(() => {
        const adStorage = cookieConsent?.adStorage ? 'granted' : 'denied'
        const analyticsStorage = cookieConsent?.analyticsStorage ? 'granted' : 'denied'

        // Check if window.gtag is there
        if (typeof window !== 'undefined' && typeof window.gtag !== 'undefined') {
            window.gtag("consent", 'update', {
                'analytics_storage': analyticsStorage,
                'ad_storage': adStorage,
            });
            console.log("Cookies set")
        } else {
            console.warn("gtag is not defined, cannot update consent.");
        }

        setLocalStorage("cookie_consent", cookieConsent);

    }, [cookieConsent]);


    return (
        <div
            className={`my-10 mx-auto max-w-[90%] md:max-w-screen-sm
                        fixed bottom-1 left-0 right-0 
                        ${cookieConsent != null ? "hidden" : "flex"} px-3 md:px-4 py-3 justify-between items-center flex-col gap-4 text-[17px]  
                        bg-[#2E3A59] rounded-lg shadow-white shadow z-50`}
        >
            <div className='flex flex-col sm:flex-row items-center justify-between gap-y-3 w-full'>
                <div className='text-center sm:text-left text-stone-200 w-fit min-w-[50%]'>
                    <Link href="/datapolicy" aria-label='Link to Data Policy'><p>This site uses <span className='font-bold text-primary hover:underline duration-1000'>Cookies.</span></p></Link>
                </div>
                <div className='flex gap-4 items-center justify-center w-full'>
                    <Button onClick={() => setCookieConsent({ adStorage: false, analyticsStorage: true })} size="md" variant="ghost" className='max-w-[33%] flex flex-col items-center justify-center' aria-label='Accept required Cookies'>
                        <span>Accept</span>
                        <span className='text-[11px]'>(required only)</span>
                    </Button>
                    <Button onClick={() => setCookieConsent({ adStorage: true, analyticsStorage: true })} size="md" variant="secondary" className='max-w-[60%]' aria-label='Accept all Cookies'>
                        Accept All
                    </Button>
                </div>
            </div>
        </div>
    )
};

this should run as fas as i am concerned. I tried it with adding GoogleAnalytics at different places in the layout and also changes in the cookie banner. So far nothing which would help. It is just always the same error. At least with the conditionals regarding the if statement for window.gtag the website runs. But this still hinders data collection.

thanks in advance!

2

There are 2 best solutions below

0
F F On

I think what is happening is typescript doens't know your window now has gtag, as you probably added it in a script inside _app or something like that I'm guessing.

If it's only used in the CookieBanner component you could add

declare global {
  interface Window {
    gtag: any;
  }
}

at the top. Otherwise you'll need to add it to a higher level component.

0
gluck0101 On

To resolve this problem, simply declare the 'gtag' function globally in your Next.js project. One way to do this is to create declaration files for global variables. Here's how:

  1. Create a new Typescript declaration file in your project root directory.

custom.d.ts

interface Window {
   gtag: (...args: any[]) => void;
}
  1. Configure tsconfig.json to include the path to the directory where your declaration files are located to ensure that the typescript compiler recognizes them.

    {
       "compiler options": {
          "include": ["next-env.d.ts", "custom.d.ts"]
       }
    }
    
  2. Implementation in code:

    // your code
    
    declare global {
       interface Window {
           gtag: (...args: any[]) => void;
       }
    }
    
    // your code