Next.js: onRouteChangeCompleted not called on first page load

Created on 25 Jun 2018  路  15Comments  路  Source: vercel/next.js

Bug report

Describe the bug

Should Router.onRouteChangeCompleted be called on first page load? If not, what is the recommended way to track page views?

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Close nextjs-starter. git clone https://github.com/iaincollins/nextjs-starter.git
  2. Add the following just after the imports in layout.js
Router.onRouteChangeComplete = url =>
  console.log('Route changed to: ', url);
  1. npm I && run dev
  2. Visit localhost:3000 and observe there is no log for the initial page load. If you navigate around, you see the subsequent route changes.

Expected behavior

I would expect there to be a simple way to hook into page changes for tracking purposes, including the first page load/

  • OS: macOS
  • Browser: tested in Firefox
  • Version of Next.js: 6.0.0
feature request

Most helpful comment

Might be easier to just do something like this in _app, outside the component.

const isBrowser = typeof window !== 'undefined';

if(isBrowser) {
  // For the first page load
  trackPageview(window.location.href)

  // Subsequent route changes
  Router.onRouteChangeComplete = _url => trackPageView(window.location.href)
}

All 15 comments

I believe the function is called onRouteChangeComplete not onRouteChangeCompleted, although I see you have that correct in your code.

For initial page load, use componentDidMount() and withRouter to access route, then onRouteChangeComplete for subsequent navigations.

At least, that's what we have that works.

@mherodev how do you prevent double-counts with your method?

ok i ended up using something like this in my HOC:

````
componentDidMount() {
this.trackPageview();
Router.router.events.on("routeChangeComplete", this.trackPageview);
}

// Cleanup, prevents multiple pageviews being counted for a single route.
componentWillUnmount() {
Router.router.events.off("routeChangeComplete", this.trackPageview);
}

trackPageview() {
// you should have taken the url from the Router
if (url !== this.lastTrackedPath) {
gtag.pageview(url);
this.lastTrackedPath = url;
}
}
````

edit: added lastTrackedPath check

Might be easier to just do something like this in _app, outside the component.

const isBrowser = typeof window !== 'undefined';

if(isBrowser) {
  // For the first page load
  trackPageview(window.location.href)

  // Subsequent route changes
  Router.onRouteChangeComplete = _url => trackPageView(window.location.href)
}

yes. if you need the component's props for any reason i think componentDidMount will still be necessary

i went with just

  componentDidMount() {
    this.trackEvent();
    Router.onRouteChangeComplete = url => this.trackEvent();
  }

because the unmount was unnecessary

Seems a bit unnecessary when you're not using props. Also you rely on the next page to overwrite your listener if the user leaves this page. If it comes back you dont want it fire twice right?

i'm using props so i need it. i've been testing throughout and it is not firing twice (or more) as i move back and forth

Any updates on this ?
i am trying the same, but onRouteChangeComplete is not being called on Mount..

Also, how do prevent double counts..

ok i ended up using something like this in my HOC:

  componentDidMount() {
    this.trackPageview();
    Router.router.events.on("routeChangeComplete", this.trackPageview);
  }

  // Cleanup, prevents multiple pageviews being counted for a single route.
  componentWillUnmount() {
    Router.router.events.off("routeChangeComplete", this.trackPageview);
  }

  trackPageview() {
    // you should have taken the url from the Router
    if (url !== this.lastTrackedPath) {
      gtag.pageview(url);
      this.lastTrackedPath = url;
    }
  }

edit: added lastTrackedPath check

Still a bug, doesnt get triggered when the same page is loaded on the client side...

There must be a better way to call onRouteChangeCompleted on initial page load..

This is expected behavior. Check the various analytics examples.

Well, if you need to have a control on initial loading, you might use loadable.

It works perfect for me.

I have this working, but there is one caveat,

Since I'm using GTM I also need to send the title of the loaded page (Note title is set dynamically in the client for some pages).

And if we try getting the title with document.title we get the server-rendered title of the page.
Is there any way we can have a trigger in _app.js which is similar to routerChangeComplete for the first page load event.

For those that are still unsatisfied at this point in the thread, here's hacky workaround to get the pageview tracked on initial load. I determined that routeChangeComplete is only called if the router has actually been used to change routes, OR if query parameters are present on the initial load, although I couldn't find this documented anywhere.

  const isBrowser = typeof window !== "undefined";

  // hacky way to ensure page-tracking is called on initial page load:
  const [initialRouteTracked, setInitialRouteTracked] = useState(false);
  if (isBrowser && !initialRouteTracked && window.location.search === "") {
    gtag.pageview(window.location.href);
    setInitialRouteTracked(true);
  }

  // use the setup from the examples directory as normal
  const router = useRouter();
  useEffect(() => {
    const handleRouteChange = (url) => {
      gtag.pageview(url);
    };
    router.events.on("routeChangeComplete", handleRouteChange);
    return () => {
      router.events.off("routeChangeComplete", handleRouteChange);
    };
  }, [router.events]);

Here's a solution I came up with:

const App = (...) => {
  const { pathname } = useRouter();

  useEffect(() => {
    Analytics.page(pathname);
  }, [pathname]);

  return (...)
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

sospedra picture sospedra  路  3Comments

pie6k picture pie6k  路  3Comments

flybayer picture flybayer  路  3Comments

irrigator picture irrigator  路  3Comments

knipferrc picture knipferrc  路  3Comments