In Part One of this series, prior to the release of Next v11, we covered configuring GA4 with Nextjs.
Since then, Nextjs has rolled out a next/script component which provides an alternative to dangerously escaping inner html -- a previously necessary evil when working with vanilla script tags.
Bonus: it's also more performant
Therefore, it's no longer necessary to live dangerously in the world of HTML -- semantics aside, we'll move the script tags we configured in _document.tsx
in the first article over to _app.tsx
.
Important Aside: Scripts must be instantiated above the Head (next/head) tag in Next.js pages and must never be used with the head in next/document
This syntactically sexy implementation can be executed as follows:
const Noop: FC = ({ children }) => <>{children}</>;
export default function NextApp({
Component,
pageProps
}: AppContext & AppInitialProps) {
const LayoutNoop = (Component as any).LayoutNoop || Noop;
const apolloClient = useApollo(pageProps);
useEffect(() => {
document.body.classList?.remove('loading');
}, []);
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url: URL) => {
gtag.pageview(url);
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return (
<>
<Script
async
strategy='lazyOnload'
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<Script strategy='afterInteractive'>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`}
</Script>
<Head nextSeoProps={NextSEO} />
<ApolloProvider client={apolloClient}>
<AuthProvider>
<GoogleFacebookProvider>
<LayoutNoop pageProps={pageProps}>
<Component {...pageProps} />
</LayoutNoop>
</GoogleFacebookProvider>
</AuthProvider>
</ApolloProvider>
</>
);
}
For those of you curious about various methods available when leveraging the new <Script />
component, you can read more here
Before eagerly firing up dev or deploying these changes to preview/production, be sure to remove the following XSS honeypot from the Head of _document.tsx
// ...
<Head>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());gtag('config',
'${GA_TRACKING_ID}', {
page: window.location.pathname
});
`
}}
/>
</Head>
// ...
Real Time Metric Reporting? Yes.
For those of you interested in sending real-time metrics to your google analytics property, you're in luck. Below your default export in _app.tsx
, include the following function, and you'll have real-time metrics pinging in your GA4 dashboard before you know it
export function reportGAVitals({
id,
name,
label,
value
}: NextWebVitalsMetric) {
if (typeof window !== 'undefined')
window.gtag('event', name, {
event_category: label === 'web-vital' ? 'Web Vitals' : 'Next.js custom metric',
value: Math.round(name === 'CLS' ? value * 1000 : value), // values must be integers
event_label: id, // id unique to current page load
non_interaction: true // avoids affecting bounce rate.
} as Gtag.EventParams);
}
Recall that the Gtag.EventParams
type from the @types/gtag.js
package is globally available for consumption with 0 imports required -- configured in a root index.d.ts
file as follows:
index.d.ts
/// <reference types="gtag.js" />
declare module 'gtag.js';
That's it for now my fellow TypeScript fiends, but expect more strongly typed Nextjs articles to come! Feedback on topics you'd like to see covered with Next + TypeScript would be appreciated -- anything from graphql-codegen to apollo config to api routes to flawlessly typing dynamic routes with fallback set to true in a headless context and so on and so forth.
Cheers
Top comments (1)
Good article mate, set a GA in typescript is a pain, this articles answering a lot. One question, is there are any way to test GA in local environment or must be deployed to hosting first. This is cumbersome to deploy the code which possibly doesn't ready for production!