GoatCounter Analytics for NextJS

GoatCounter Analytics is a privacy-friendly web analytics tool perfect for small websites like blogs. It is free and open source, and it also has a self hosted option.

I started using it in this blog, to see how a better understanding the traffic, while still being privacy-friendly. Before, I was using the panel provided by Cloudflare, which is also a good option. But it didnt provide me with enough information like the number of page views per page or article or the Referrer information.

Why Use GoatCounter Analytics?

Privacy is a growing concern on the web, and more and more users are becoming aware of the importance of protecting their online data. GoatCounter is a great solution for website owners who want to respect their users privacy while still gathering useful data about their website traffic. It is a simple and lightweight tool that provides essential data like page views, referrer information, and more, without tracking any personal data or using cookies.

Getting Started with GoatCounter Analytics

To use GoatCounter Analytics, the first thing you need to do is to create an account on the GoatCounter website for your site. Once you have created an account, GoatCounter will provide you with a script tag that you need to add to your website.

<script
  data-goatcounter="https://example.goatcounter.com/count"
  async
  src="//gc.zgo.at/count.js"
></script>

Integrating GoatCounter with NextJS

To integrate GoatCounter with your NextJS website, youll need to add a script tag to your _app.tsx file, which will ensure that the script is loaded on every page. Its essential to use the afterInteractive strategy to avoid impacting page load performance. Additionally, youll need to hook up with the router events to send page changes to GoatCounte.

  1. Add the GoatCounter script tag to your pages/_app.tsx file:

    // pages/_app.tsx
    function MyApp({ Component, pageProps }) {
      return (
        <>
          <Script
            strategy="afterInteractive"
            data-goatcounter={`https://${API}.goatcounter.com/count`}
            src="//gc.zgo.at/count.js"
          ></Script>
          <Component {...pageProps} />
        </>
      );
    }
    

    Remember to put the script outside the Header component, otherwise it wont work and the script will not be loaded correctly.

  2. The script will add a magic pixel on the first load, which will phone home to the GoatCounter server and registered the visit. But it wont work for the rest of the of the page views since NextJS is a SPA. So we need to hook up to the router events to send the page views to GoatCounter.

  3. Now you need to register a event listener to the router, so when the page changes well send the information to the GoatCounter server. To do that, well use the useRouter:

    import { useEffect } from "react";
    import { useRouter } from "next/router";
    
    export default function MyApp({ Component, pageProps }) {
      const router = useRouter();
    
      useEffect(() => {
        const handleRouteChange = (url) => {
          window.goatcounter.count({
            path: url,
          });
        };
    
        router.events.on("routeChangeComplete", handleRouteChange);
    
        return () => {
          router.events.off("routeChangeComplete", handleRouteChange);
        };
      }, []);
    
      return <Component {...pageProps} />;
    }
    

    We use the useEffect to add the event listener only once, and we remove it when the component is unmounted.

Now you can go to your GoatCounter dashboard and see the page views. You can also see the referrer information, which is very useful to see where your traffic is coming from.

Conclusion

GoatCounter is a privacy-friendly analytics tool that provides useful data about website traffic without tracking any personal data. By following the steps outlined in this article, you can integrate GoatCounter Analytics with your NextJS web application and start gathering useful data about your website traffic while respecting your users privacy.

Update 2023-04-03 app directory

Since the NextJS canary version and the future 13.3 release will support static exports, I decided to move the pages directory to the app directory.

In the new layout the useRouter is deprecated in favour of the new and more ergonomic usePathname and useSearchParams hooks. So I needed to update the above script to support the new API.

I also improved the component, using the state hook to wait for the goatcounter script to be loaded before using it.

The component consists in the following:

export function GoatCounter({ api }: GoatCounterProps) {
  const pathname = usePathname();
  const [goatcounter, setGoatcounter] = useState<GoatCounter | null>(null);

  useEffect(() => {
    // We need to wait for the script to load before we can use it.
    if (!goatcounter) {
      return;
    }

    goatcounter.count({
      //path: location.pathname + location.search + location.hash,
      path: pathname,
    });
  }, [pathname, goatcounter]);

  return (
    <Script
      strategy="afterInteractive"
      data-goatcounter={`https://${api}.goatcounter.com/count`}
      src="//gc.zgo.at/count.js"
      onLoad={() => {
        if (!window.goatcounter) {
          throw new Error("goatcounter object is not defined");
        }

        setGoatcounter(window.goatcounter);
      }}
    />
  );
typescript}

Bonus

To make the typescript compiler happy, I added the following to the types/index.d.ts file, you also need to specify it into the tsconfig file:

/**
 * GoatCounter script object.
 */
interface GoatCounter {
  count: (options: { path: string }) => void;
}

/**
 * Declare the global goatcounter that is included as a script.
 */
interface Window extends Window {
  goatcounter?: GoatCounter;
}