diff --git a/package.json b/package.json index d396878f8fd..b7d9b7b9b0b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@swc/helpers": "^0.4.11", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", + "@types/gtag.js": "^0.0.11", "@types/jest": "^29.0.0", "@types/node": "^18.7.16", "@types/rimraf": "^3.0.2", diff --git a/packages/toolpad-app/next.config.mjs b/packages/toolpad-app/next.config.mjs index 8b16122c86f..6dfb7cfe8b1 100644 --- a/packages/toolpad-app/next.config.mjs +++ b/packages/toolpad-app/next.config.mjs @@ -167,6 +167,6 @@ export default /** @type {import('next').NextConfig} */ ({ source: '/:path*', headers: securityHeaders, }, - ] + ]; }, }); diff --git a/packages/toolpad-app/pages/_app.tsx b/packages/toolpad-app/pages/_app.tsx index caa7759cf67..c2bb72a1054 100644 --- a/packages/toolpad-app/pages/_app.tsx +++ b/packages/toolpad-app/pages/_app.tsx @@ -1,27 +1,46 @@ import * as React from 'react'; import Head from 'next/head'; -import App, { AppContext, AppProps } from 'next/app'; +import App, { AppContext, AppProps, NextWebVitalsMetric } from 'next/app'; import { ThemeProvider } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import { CacheProvider, EmotionCache } from '@emotion/react'; import { QueryClientProvider } from '@tanstack/react-query'; import { LicenseInfo } from '@mui/x-data-grid-pro'; +import { useRouter } from 'next/router'; import theme from '../src/theme'; import createEmotionCache from '../src/createEmotionCache'; import { MUI_X_PRO_LICENSE } from '../src/constants'; import { queryClient } from '../src/api'; +import { reportWebVitalsToGA, setGAPage } from '../src/utils/ga'; LicenseInfo.setLicenseKey(MUI_X_PRO_LICENSE); // Client-side cache, shared for the whole session of the user in the browser. const clientSideEmotionCache = createEmotionCache(); +export const reportWebVitals = (metric: NextWebVitalsMetric): void => { + reportWebVitalsToGA(metric); +}; + interface MyAppProps extends AppProps { emotionCache?: EmotionCache; } export default function MyApp(props: MyAppProps) { const { Component, emotionCache = clientSideEmotionCache, pageProps } = props; + + const router = useRouter(); + + React.useEffect(() => { + router.events.on('routeChangeComplete', setGAPage); + router.events.on('hashChangeComplete', setGAPage); + + return () => { + router.events.off('routeChangeComplete', setGAPage); + router.events.off('hashChangeComplete', setGAPage); + }; + }, [router.events]); + return ( diff --git a/packages/toolpad-app/pages/_document.tsx b/packages/toolpad-app/pages/_document.tsx index 92b6aad8305..2436fa1d04c 100644 --- a/packages/toolpad-app/pages/_document.tsx +++ b/packages/toolpad-app/pages/_document.tsx @@ -9,10 +9,12 @@ import Document, { } from 'next/document'; import createEmotionServer from '@emotion/server/create-instance'; import serializeJavascript from 'serialize-javascript'; +import Script from 'next/script'; import theme from '../src/theme'; import createEmotionCache from '../src/createEmotionCache'; import config, { RuntimeConfig } from '../src/config'; import { RUNTIME_CONFIG_WINDOW_PROPERTY } from '../src/constants'; +import { GA_ID } from '../src/utils/ga'; interface ToolpadDocumentProps { config: RuntimeConfig; @@ -95,6 +97,27 @@ export default class MyDocument extends Document { )}] = ${serializeJavascript(this.props.config, { ignoreFunction: true })}`, }} /> + + {/* Global site tag (gtag.js) - Google Analytics */} +