Support for server components in Next 13
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)?
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
GoogleAnalyticscomponent, 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
useEffectcall inusePageViews.The
next-authteam 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
useEffectcalls)?
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
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.
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
Thanks for the info. I will try your suggestions.
Based on the info above, I got my answer and it worked for me. Here is my solution:
- 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
});
`,
}}
/>
</>
);
}
- Wrap your html elements with the
AnalyticsProvidercomponent inapp/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>
);
}
Based on the info above, I got my answer and it worked for me. Here is my solution:
- 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 }); `, }} /> </> ); }
- Wrap your html elements with the
AnalyticsProvidercomponent inapp/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
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
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 />;
}
Create a client component in your providers/components dir. Then, use the component in your layout file.
- GoogleAnalytics.tsx
"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 />;
}
- app/layout.tsx
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>
);
}