MauricioRobayo / nextjs-google-analytics

Google Analytics for Next.js
MIT License
535 stars 49 forks source link

Support for server components in Next 13 #304

Open EvHaus opened 2 years ago

EvHaus commented 2 years ago

Thanks for the excellent library. I noticed that in Next 13, the library isn't supported with server components. If you want to use the GoogleAnalytics component, you must set that component to "use client". Since in most cases I want <GoogleAnalytics /> to be set at the root in (app/layout.tsx) it means that I need the set the top-level component of my app to be a client component, defeating all the benefits of Next 13's server components.

If I try to use <GoogleAnalytics /> in a server component I get this error:

error - (sc_server)/node_modules/next/dist/shared/lib/router-context.js (8:37) @ eval
error - TypeError: _react.default.createContext is not a function
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/shared/lib/router-context.js:8:38)
    at Object.(sc_server)/./node_modules/next/dist/shared/lib/router-context.js (/home/myproject/.next/server/app/page.js:880:1)
    at __webpack_require__ (/home/myproject/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/client/router.js:24:22)
    at Object.(sc_server)/./node_modules/next/dist/client/router.js (/home/myproject/.next/server/app/page.js:671:1)
    at __webpack_require__ (/home/myproject/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/router.js:2:18)
    at Object.(sc_server)/./node_modules/next/router.js (/home/myproject/.next/server/app/page.js:1177:1)
    at __webpack_require__ (/home/myproject/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/nextjs-google-analytics/dist/hooks/usePageViews.js:7:18) {
  type: 'TypeError',
  page: '/'
}

I suspect it's because of the useEffect call in usePageViews.

The next-auth team was also recently struggling with a similar issue I believe (see comment).

I'm curious of there is any way to make this library work in a server component (ie. not use any useEffect calls)?

webbipage commented 1 year ago

Thanks for the excellent library. I noticed that in Next 13, the library isn't supported with server components. If you want to use the GoogleAnalytics component, you must set that component to "use client". Since in most cases I want <GoogleAnalytics /> to be set at the root in (app/layout.tsx) it means that I need the set the top-level component of my app to be a client component, defeating all the benefits of Next 13's server components.

If I try to use <GoogleAnalytics /> in a server component I get this error:

error - (sc_server)/node_modules/next/dist/shared/lib/router-context.js (8:37) @ eval
error - TypeError: _react.default.createContext is not a function
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/shared/lib/router-context.js:8:38)
    at Object.(sc_server)/./node_modules/next/dist/shared/lib/router-context.js (/home/myproject/.next/server/app/page.js:880:1)
    at __webpack_require__ (/home/myproject/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/client/router.js:24:22)
    at Object.(sc_server)/./node_modules/next/dist/client/router.js (/home/myproject/.next/server/app/page.js:671:1)
    at __webpack_require__ (/home/myproject/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/router.js:2:18)
    at Object.(sc_server)/./node_modules/next/router.js (/home/myproject/.next/server/app/page.js:1177:1)
    at __webpack_require__ (/home/myproject/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/nextjs-google-analytics/dist/hooks/usePageViews.js:7:18) {
  type: 'TypeError',
  page: '/'
}

I suspect it's because of the useEffect call in usePageViews.

The next-auth team was also recently struggling with a similar issue I believe (see comment).

I'm curious of there is any way to make this library work in a server component (ie. not use any useEffect calls)?

You may want to create a custom component with “use client” flag and return <GoogleAnalytics /> to solve this problem until the package become compatible to Next.js 13. It is a official method provided in the beta documentation.

See https://beta.nextjs.org/docs/rendering/server-and-client-components#third-party-packages

kachkaev commented 1 year ago

Another problem with the /app folder is the lack of router events in next/navigation: https://github.com/vercel/next.js/issues/45499. This may get fixed by https://github.com/vercel/next.js/pull/46391.

airtonix commented 1 year ago

In addition to the above you'd also need to account for how ga wants to track server side apps:

  const gtmDomain = process.env.NEXT_PUBLIC_GTM_DOMAIN;

  gtag('config', '${gaMeasurementId}', {
     page_path: window.location.pathname,
     transport_url: 'https://${gtmDomain}',
     first_party_collection: true
  });

https://github.com/EmilianoGarciaLopez/nextjs-google-analytics-GTM/blob/main/src/GoogleAnalytics.tsx#L26-L27

LZL0 commented 1 year ago

Thanks for the info. I will try your suggestions.

aifuxi commented 1 year ago

Based on the info above, I got my answer and it worked for me. Here is my solution:

  1. create a custom component with “use client” flag.

// AnalyticsProvider component 'use client';

import React from 'react';

import Script from 'next/script';

type Props = { children: React.ReactNode };

export default function AnalyticsProvider(children: Props) { // if you use env file // const gaMeasurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID; // const domain = process.env.NEXT_PUBLIC_GTM_DOMAIN; // const reportDomain = process.env.NEXT_PUBLIC_GTM_REPORT_DOMAIN;

// or just hard code const gaMeasurementId = 'your_measurement_id'; const domain = 'https://www.googletagmanager.com'; const reportDomain = 'https://www.google-analytics.com';

return ( <> {children} {/ I tried importing the Google Analytics component from the nextjs-google-analytics package, but it didn't work. So I chose to write the script manually. /} {/ /} <Script id="google-analytics-js-cdn" src={${domain}/gtag/js?id=${gaMeasurementId}} strategy="afterInteractive" /> <Script id="google-analytics" strategy="afterInteractive" dangerouslySetInnerHTML={{ __html: ` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date());

          gtag('config', '${gaMeasurementId}', {
             page_path: window.location.pathname,
             transport_url: '${reportDomain}',
             first_party_collection: true
          });
      `,
    }}
  />
</>

); }

2. Wrap your html elements with the `AnalyticsProvider` component in `app/layout.tsx`
```tsx
// app/layout.tsx

// import the AnalyticsProvider you just created
import AnalyticsProvider from 'xxxx';

type Props = {
  children: React.ReactNode;
};

export default async function RootLayout({ children }: Props) {
  return (
    <html lang="en">
      <body>
        <AnalyticsProvider>
          {/* your html element */}
          {children}
        </AnalyticsProvider>
      </body>
    </html>
  );
}
marleymwangi commented 1 year ago

Based on the info above, I got my answer and it worked for me. Here is my solution:

  1. create a custom component with “use client” flag.
// AnalyticsProvider component
'use client';

import React from 'react';

import Script from 'next/script';

type Props = { children: React.ReactNode };

export default function AnalyticsProvider(children: Props) {
  // if you use env file
  // const gaMeasurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
  // const domain = process.env.NEXT_PUBLIC_GTM_DOMAIN;
  // const reportDomain = process.env.NEXT_PUBLIC_GTM_REPORT_DOMAIN;

  // or just hard code
  const gaMeasurementId = 'your_measurement_id';
  const domain = 'https://www.googletagmanager.com';
  const reportDomain = 'https://www.google-analytics.com';

  return (
    <>
      {children}
  {/* I tried importing the Google Analytics component from the nextjs-google-analytics package, but it didn't work.
So I chose to write the script manually. */}
        {/* <GoogleAnalytics trackPageViews /> */}
      <Script
        id="google-analytics-js-cdn"
        src={`${domain}/gtag/js?id=${gaMeasurementId}`}
        strategy="afterInteractive"
      />
      <Script
        id="google-analytics"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());

              gtag('config', '${gaMeasurementId}', {
                 page_path: window.location.pathname,
                 transport_url: '${reportDomain}',
                 first_party_collection: true
              });
          `,
        }}
      />
    </>
  );
}
  1. Wrap your html elements with the AnalyticsProvider component in app/layout.tsx
// app/layout.tsx

// import the AnalyticsProvider you just created
import AnalyticsProvider from 'xxxx';

type Props = {
  children: React.ReactNode;
};

export default async function RootLayout({ children }: Props) {
  return (
    <html lang="en">
      <body>
        <AnalyticsProvider>
          {/* your html element */}
          {children}
        </AnalyticsProvider>
      </body>
    </html>
  );
}

@aifuxi snippet worked for me

aamir-madari commented 1 year ago

Forgive me for being a bit blonde, but does the above snippet mean it's a bit redundant to use this package for the time being when using server components in Next.js 13

anthonyholmes commented 1 year ago

You can create a client component that wraps the package component. This can be used in app/layout

"use client";
import { GoogleAnalytics } from "nextjs-google-analytics";

export default function Analytics({ measurementId }) {
  return <GoogleAnalytics gaMeasurementId={measurementId} trackPageViews />;
}
alipiry commented 11 months ago

Create a client component in your providers/components dir. Then, use the component in your layout file.

"use client";

import type { NextWebVitalsMetric } from "next/app";
import { event, GoogleAnalytics as GAnalytics } from "nextjs-google-analytics";

// Send NextJS Web Vitals to GA
export function reportWebVitals({
  id,
  name,
  label,
  value,
}: NextWebVitalsMetric) {
  event(name, {
    category: label === "web-vital" ? "Web Vitals" : "Next.js custom metric",
    value: Math.round(name === "CLS" ? value * 1000 : value), // values must be integers
    label: id, // id unique to current page load
    nonInteraction: true, // avoids affecting bounce rate.
  });
}

export default function GoogleAnalytics() {
  return <GAnalytics trackPageViews />;
}
import { ReactNode } from "react";
import Header from "@/ui/header/header";
import Footer from "@/ui/footer/footer";
import GoogleAnalytics from "@/providers/google-analytics";

interface RootLayoutProps {
  children: ReactNode;
}

export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <html lang="en">
      <GoogleAnalytics />
      <body>
        <Header />
        <div>{children}</div>
        <Footer />
      </body>
    </html>
  );
}