How to use google analytics with next.js app?
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
To correctly initialize gtag
, do the following in _document.js
or wherever you defined Head
:
import { 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
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
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"
Another way that worked well to me without dangerouslySetInnerHTML
:
- Create a new js file in the public folder with the contents in
dangerouslySetInnerHTML
. - Add that created js file to the
_document.js
file.
The sample for what my _document.js
returned:
<Html>
<Head>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxx-x"></script>
<script src="/ga.js" async></script>
{/*other scripts*/}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>