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 didn’t 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, you’ll need to add a script
tag to your _app.tsx
file, which will ensure that the script is loaded on
every page. It’s essential to use the afterInteractive
strategy to avoid
impacting page load performance. Additionally, you’ll need to hook up with the
router events to send page changes to GoatCounte.
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 won’t work and the script will not be loaded correctly.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 won’t 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.
Now you need to register a event listener to the router, so when the page changes we’ll send the information to the GoatCounter server. To do that, we’ll 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:
- Getting the
pathname
, and optionally the query string, with theusePathname
hook - A
goatcounter
state value for the GoatCounter script since we cannot access the window object in the static export - A
useEffect
hook to run the code only when the path changes or the script is loaded - Finally the
Script
component to load the script, and when loaded it will update thegoatcounter
state value
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;
}