Andrei
Andrei

Reputation: 1873

Next.js - How to make sure <title> is set by the time the history change event fires?

In my Next.js app, I'm setting the <title> tag for individual pages using the recommended method:

import Head from 'next/head'

export default () => <>
    <Head><title>My page title</title></Head>
</>

Here's the problem: when the history change event fires, the value of document.title doesn't always match the current URL.

You can test it yourself:

Router.events.on("routeChangeComplete", () => {
    if ('browser' in process) {
        console.log('--------');
        console.log(window.location.href);
        console.log(document.title);
    }
});

Navigating between pages, you should observe that URL & title are often mismatched. The value of URL is always right, but the value of title is all over the place. It can have:

This is an issue when using analytics, specifically GTM - Google Tag Manager, which uses the current URL & page title to uniquely identify visited pages.

I've had this issue with Next.js 7, and upgrading to 8 hasn't fixed it.

Do you know of any way to solve this problem? Maybe delaying the history change event until the first render of a component under /pages/?

Thanks!

Upvotes: 0

Views: 3222

Answers (3)

Doan Thai
Doan Thai

Reputation: 685

You can use setInterval

const firstPageViewEvent = setInterval(() => {
  if (document.title) {
    // send pageview event
    clearInterval(firstPageViewEvent);
  }
}, 200);

Upvotes: -1

Andrei
Andrei

Reputation: 1873

I found a work-around by intercepting events sent to window.dataLayer.push and adding a one-second delay in case the event is gtm.historyChange.

Here's my GtagScript component that I'm adding to <Head> under _document.js:

export const GtagScript = () => {

    function intercept() {
        const scriptTag = document.querySelector('#gtm-js');
        if (scriptTag !== null)
            scriptTag.addEventListener('load', () => {
                window.dataLayer.pushOrig = window.dataLayer.push;

                window.dataLayer.push = (e) => {
                    if (e.event === 'gtm.historyChange') {
                        setTimeout(function () {
                            window.dataLayer.pushOrig(e);
                            console.log(`URL: ${window.location.href} Title: ${document.title}`);
                        }, 1000);
                    } else {
                        window.dataLayer.pushOrig(e);
                    }
                };
            });
    }


    return <>
        <script
            id="gtm-js"
            async
            src={`https://www.googletagmanager.com/gtm.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}');
            ${intercept.toString()}
            intercept();`
            }}
        />
    </>
};

I'll wait a while to see if an official fix comes from the ZEIT team. My solution doesn't actually answer the question. It doesn't set <title>, it just defers the event, which isn't optimal.

Upvotes: 1

evgeni fotia
evgeni fotia

Reputation: 4810

This is a small hack. setTimeout(()=>{console.log(document.title)}, 0)

Upvotes: 1

Related Questions