Tracking page views with Fathom and Next.js

Everyone likes to see metrics, even if it's just vanity. It might not be real validation of my writing, but it's nice to know at least people are visiting my tiny corner of the web. After reading, Damian Bradfield's book, The Trust Manifesto, about big data and privacy it got me thinking. How much do I need to track and know about my audience?

I knew I was going to need a solution to help me track visitors but which one? My search began with the usual suspects such as Google, Heap, and Mixpanel. They're all great products, but they follow you around a lot. I thought all was lost until I came across FathomFathom is a simple and private website analytics platform. I started reading a few of their posts and found their post describing how they handle anonymisation particularly interesting. Their views on privacy, coupled with the fact that they are a small company, and I was sold.

At first, setting up Fathom was as simple as adding their snippet. However, once I pushed my site live, I noticed page views weren't getting tracked accurately. Server renders got logged correctly, but page changes on the client were missing (unless no-one was browsing more than one page). The problem was pretty obvious, Fathom's snippet didn't account for single-page applications, and the solution on their site didn't seem to work with Next.js.

The solution was pretty simple, use Fathom's snippet to log all server renders from _document.js, and track all subsequent clientside route changes in _app.js via Next.js's router events.

// _document.js
import Document, {
  Html,
  Head,
  Main,
  NextScript
} from "next/document";

const __html = `(function(f, a, t, h, o, m){
  a[h]=a[h]||function(){
  (a[h].q=a[h].q||[]).push(arguments)
  };
  o=f.createElement('script'),
  m=f.getElementsByTagName('script')[0];
  o.async=1; o.src=t; o.id='fathom-script';    
  m.parentNode.insertBefore(o,m)
  })(document, window, 'https://cdn.usefathom.com/tracker.js', 'fathom');
  fathom('set', 'siteId', '<YOUR_TRACKING_CODE>');  
  fathom("trackPageview");`;

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <Main />
          <NextScript />
          <script dangerouslySetInnerHTML={{ __html }} />        
        </body>
      </Html>
    )
  }
}

// _app.js
import Router from 'next/router';

const trackPageView = () => {
  if (typeof window !== "undefined" && typeof window.fathom !== "undefined") {
    window.fathom("trackPageview");
  }
};

Router.events.on("routeChangeComplete", trackPageView);

export default ({ Component, pageProps }) => {
  return <Component {...pageProps} />
}

And that's it.