Search code examples
next.jsgoogle-tag-manager

Adding tag manager to next js, confusing docs


I am adding tag manager to my next.js site (next version 12.0.4) and the docs here https://nextjs.org/docs/basic-features/script say that I can use next/script in the _document.js file.

When I follow the instructions I then get an error that points me to this page https://nextjs.org/docs/messages/no-script-in-document-page saying I cannot use next/script in the _document.js page, this page points back to the original page.

I'm confused, what is the correct implementation?


Solution

  • After having a dig around I came across this post: Next 11 and adding Script tags not working. No scripts are rendered

    Basically saying scripts where not working, which once I had ignored the warnings and tried in my solution I found also to be the case.

    The solution to use next/head in my _app.js seems to work fine.

    read the above comment by @juliomalves for the explanation as to why this is to be done in _app.js

    import Script from "next/script";
    
        const App = ({ Component, pageProps }) => (
          <>
            <Script
            id="tagmanager-main"
            strategy="afterInteractive"
            async
            src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_MEASUREMENT_ID}`}
          ></Script>
          <Script
            id="tagmanager-setup"
            strategy="afterInteractive"
            dangerouslySetInnerHTML={{
              __html: `
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());
                gtag('config', '${process.env.NEXT_PUBLIC_MEASUREMENT_ID}');
              `,
            }}
          />
            {/* eslint-disable-next-line react/jsx-props-no-​spreading */}
            <Component {...pageProps} />
          </>
        );
        
        export default App;
    

    This was my _document.tsx, maybe there was something in there stopping it from working, but I am happy enough with the working solution above.

    import * as React from 'react';
    // eslint-disable-next-line @next/next/no-document-import-in-page
    import Document, { Html, Head, Main, NextScript } from 'next/document';
    import createEmotionServer from '@emotion/server/create-instance';
    // import theme from '../../styles/theme';
    import createEmotionCache from '../lib/createEmotionCache';
    // eslint-disable-next-line @next/next/no-script-in-document
    import Script from 'next/script';
    
    export default class MyDocument extends Document {
      render() {
        return (
          <Html lang="en">
            <Head>
              {/* PWA primary color */}
              {/* <meta name="theme-color" content={theme.palette.primary.main} /> */}
              <link
                rel="stylesheet"
                href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
              />
              <link
                rel="stylesheet"
                href="https://fonts.googleapis.com/icon?family=Material+Icons"
              />
              <Script
                id="tagman"
                strategy="afterInteractive"
                async
                src="https://www.googletagmanager.com/gtag/js?id=G-xxxxx"
              ></Script>
              <Script
                id="tagman-datalayer"
                strategy="afterInteractive"
                dangerouslySetInnerHTML={{
                  __html: `
                    window.dataLayer = window.dataLayer || [];
                    function gtag(){dataLayer.push(arguments);}
                    gtag('js', new Date());
              
                    gtag('config', 'G-xxxxx');
                  `,
                }}
              />
    
              <script
                src={`https://maps.googleapis.com/maps/api/js?key=${
                  process.env.NEXT_PUBLIC_MAPS_API_KEY || ''
                }&libraries=&v=weekly`}
                async
              ></script>
            </Head>
            <body>
              <Main />
              <NextScript />
            </body>
          </Html>
        );
      }
    }
    
    // `getInitialProps` belongs to `_document` (instead of `_app`),
    // it's compatible with static-site generation (SSG).
    MyDocument.getInitialProps = async (ctx) => {
      // Resolution order
      //
      // On the server:
      // 1. app.getInitialProps
      // 2. page.getInitialProps
      // 3. document.getInitialProps
      // 4. app.render
      // 5. page.render
      // 6. document.render
      //
      // On the server with error:
      // 1. document.getInitialProps
      // 2. app.render
      // 3. page.render
      // 4. document.render
      //
      // On the client
      // 1. app.getInitialProps
      // 2. page.getInitialProps
      // 3. app.render
      // 4. page.render
    
      const originalRenderPage = ctx.renderPage;
    
      // You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
      // However, be aware that it can have global side effects.
      const cache = createEmotionCache();
      const { extractCriticalToChunks } = createEmotionServer(cache);
    
      ctx.renderPage = () =>
        originalRenderPage({
          // eslint-disable-next-line react/display-name, @typescript-eslint/no-explicit-any
          enhanceApp: (App: any) => (props) =>
            <App emotionCache={cache} {...props} />,
        });
    
      const initialProps = await Document.getInitialProps(ctx);
      // This is important. It prevents emotion to render invalid HTML.
      // See https://github.com/mui-org/material-ui/issues/26561#issuecomment-855286153
      const emotionStyles = extractCriticalToChunks(initialProps.html);
      const emotionStyleTags = emotionStyles.styles.map((style) => (
        <style
          data-emotion={`${style.key} ${style.ids.join(' ')}`}
          key={style.key}
          // eslint-disable-next-line react/no-danger
          dangerouslySetInnerHTML={{ __html: style.css }}
        />
      ));
    
      return {
        ...initialProps,
        // Styles fragment is rendered after the app and page rendering finish.
        styles: [
          ...React.Children.toArray(initialProps.styles),
          ...emotionStyleTags,
        ],
      };
    };