How to use google analytics with next.js app?
Solution 1
To correctly initialize gtag
, do the following in _document.js
or wherever you defined Head
:
import Document, { Head } from "next/document";
export default class MyDocument extends Document {
render() {
return (
// ...
<Head>
<script
async
src="https://www.googletagmanager.com/gtag/js?id=[Tracking ID]"
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '[Tracking ID]', { page_path: window.location.pathname });
`,
}}
/>
</Head>
);
}
}
The above will track page views on page load. To track navigation add the following to _app.js
:
import { useRouter } from 'next/router';
import { useEffect } from "react";
export default const App = () => {
const router = useRouter();
const handleRouteChange = (url) => {
window.gtag('config', '[Tracking ID]', {
page_path: url,
});
};
useEffect(() => {
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return (
// ...
);
};
See also:
- https://github.com/vercel/next.js/tree/canary/examples/with-google-analytics
- https://github.com/vercel/next.js/issues/160
Solution 2
To setup Google analytics with NextJS using Typescript
I'm using below setup for my personal site (https://github.com/GorvGoyl/Personal-Site-Gourav.io) and it's working fine without any linting errors. Analytics is enabled only for production.
- Create a Google analytics project and get Measurement ID.
- In your NextJS project, create
/lib/gtag.ts
file and add your Google Measurement ID:
export const GA_TRACKING_ID = "<INSERT_TAG_ID>";
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const pageview = (url: URL): void => {
window.gtag("config", GA_TRACKING_ID, {
page_path: url,
});
};
type GTagEvent = {
action: string;
category: string;
label: string;
value: number;
};
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const event = ({ action, category, label, value }: GTagEvent): void => {
window.gtag("event", action, {
event_category: category,
event_label: label,
value,
});
};
- Also install gtag
types
:
npm i -D @types/gtag.js
- Create
/pages/_document.tsx
:
import Document, { Html, Head, Main, NextScript } from "next/document";
import { GA_TRACKING_ID } from "../lib/gtag";
const isProduction = process.env.NODE_ENV === "production";
export default class MyDocument extends Document {
render(): JSX.Element {
return (
<Html>
<Head>
{/* enable analytics script only for production */}
{isProduction && (
<>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
</>
)}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
- Create
/pages/_app.tsx
:
import { AppProps } from "next/app";
import { useRouter } from "next/router";
import { useEffect } from "react";
import * as gtag from "../lib/gtag";
const isProduction = process.env.NODE_ENV === "production";
const App = ({ Component, pageProps }: AppProps): JSX.Element => {
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url: URL) => {
/* invoke analytics function only for production */
if (isProduction) gtag.pageview(url);
};
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}, [router.events]);
// eslint-disable-next-line react/jsx-props-no-spreading
return <Component {...pageProps} />;
};
export default App;
More info: https://gourav.io/blog/nextjs-cheatsheet
Solution 3
In your _document.js
you override the getInitialProps
method. You can also override the render
method. Simply add
render() {
return (
<Html lang={this.props.lang || "en"}>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `[google analytics tracking code here]`
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Make sure you import the required components:
import Document, { Html, Head, Main, NextScript } from "next/document"
Solution 4
Do not use the top answer here: using the native <script>
tag is forbidden and it should be defined outside of the <head>
tag.
This is the proper way to include a script tag and configure up Google Analytics in NextJS:
import Script from 'next/script'
import Head from 'next/head'
export default function Index() {
return (
<>
<Head>
<title>Next.js</title>
</Head>
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){window.dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
</>
)
}
For more info: https://nextjs.org/docs/messages/next-script-for-ga
Solution 5
This is the method recommended by next.js.
/components/GoogleAnalytics.jsx
import Script from 'next/script'
import { useEffect } from 'react'
import { useRouter } from 'next/router'
const GA_TRACKING_ID = '...'
export default () => {
const router = useRouter()
useEffect(() => {
const handleRouteChange = url => {
window.gtag('config', GA_TRACKING_ID, { page_path: url })
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return (
<>
<Script
strategy='afterInteractive'
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<Script
id='gtag-init'
strategy='afterInteractive'
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`
}}
/>
</>
)
}
/pages/_app.jsx
import GoogleAnalytics from './../components/GoogleAnalytics'
export default function App ({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
{
process.env.NODE_ENV === 'production' &&
<GoogleAnalytics />
}
</>
)
}
0xsh
Updated on February 19, 2022Comments
-
0xsh about 2 years
I'm using styled-components with next.js so my styles need to be server-side rendered, hence how can I add google analytics to my website?
I checked next.js google analytics example but as I said my _document file is different because of using styled-components.
// _document.js import React from 'react' import Document from 'next/document' import { ServerStyleSheet } from 'styled-components' class MyDocument extends Document { static async getInitialProps(ctx) { const sheet = new ServerStyleSheet() const originalRenderPage = ctx.renderPage try { ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />), }) const initialProps = await Document.getInitialProps(ctx) return { ...initialProps, styles: ( <> {initialProps.styles} {sheet.getStyleElement()} </> ), } } finally { sheet.seal() } } } export default MyDocument
-
Cully almost 4 yearsDoes this register a new page visit on all navigation changes?
-
thisismydesign almost 4 yearsIt does so on every page load. So as long as you're using Next's router, yes. For client-side navigation, you could look at this: stackoverflow.com/a/63249329/2771889
-
Cully almost 4 yearsThe nextjs router doesn't reload the page on navigation changes, though.
-
Cully almost 4 yearsI just tested this out. Pageviews are not registered on navigation changes. This is because nextjs doesn't refresh the page on navigation changes, it just changes some props and lets React re-render accordingly.
-
thisismydesign almost 4 yearsI'm using
next/link
and it does register pageviews. -
Cully almost 4 yearsDo you have a "history change trigger" set up in Google Tag Manager? That would register pageviews on history changes.
-
蔡佳峰 over 3 yearsThanks mate, I had same issue, your answer really helped!!
-
Matteo Frana over 3 yearsJust one thing: the value should not be required in the GTagEvent type and not sent if not provided.
-
dortonway over 3 yearsWhy is it recommended to do in _document.js, if the file is rendered on the back-end only?
-
Olumide over 3 years@MatteoFrana I don't seem to understand your comment above, can you shed more light? Thanks
-
Laurentiu Petrea about 3 yearsKudos! Worked like a charm!
-
cbdeveloper about 3 yearsI can confirm that this only fires once per browser refresh. No additional hits on subsequent page changes.
-
thisismydesign almost 3 yearsYou're right. Updated my answer to track navigation using
gtag
. -
Ciro Santilli OurBigBook.com over 2 yearsOn Next.js 10.2.2, a page build with the navigation part gives
Type error: Property 'gtag' does not exist on type 'Window & typeof globalThis'.
for typescript_app.tsx
instead of_app.js
BTW. Gotta have a look at the Typescript specific answer: stackoverflow.com/a/65081431/895245 -
otto over 2 yearsI get the warning
Use the `next/script` component for loading third party scripts
-
Dendi Handian over 2 yearseveryone may want to check my answer of using
next/script
-
P-A over 2 yearsthis is what I needed, thank you !
-
mbianchidev over 2 yearsI'd put the GA_TRACKING_ID in an .env file, having it in plain view doesn't seem a good idea to me
-
TJ Mazeika about 2 yearsIt appears that the
handleRouteChange
anduseEffect
are no longer needed with the newest versions of GA. In fact, it appears that you'll get double the expected page_view events if you're manually listening to router events. The newer tracking IDs start with "GA-". -
Ravi kumar about 2 yearsThanks you for the solution worked like a charm.
-
a paid nerd about 2 yearsIf you're having TypeScript errors, try installing
@types/gtag.js
-
Dan Zuzevich about 2 yearsThis results in double the page views
-
serraosays almost 2 yearsThis doesn't use
next/script
and throws a warning in Next v12+ -
serraosays almost 2 yearsThis is the right way to do this, thank you.
-
Jonathan Gruber almost 2 years@TJMazeika I can confirm this and also think the hook is no longer necessary. In the Google Analytics Debugger in Chrome you can see an event being fired with
{ event: "gtm.historyChange-v2", gtm.historyChangeSource: "pushState", ... }
on client side navigation. Adding the two<Script>
s seems sufficient now.