When I migrated this website from Next.js to SvelteKit, I had to figure out how to wire up Fathom Analytics. Fathom Analytics is an alternative to Google Analytics. It features a better user experience for website owners and is more privacy-friendly for visitors. This post expands on Matt Jennings's post How to use Fathom Analytics with SvelteKit.
Tracking page views and goals
There are usually two things you want to track with web analytics: page views and goals. Tracking page views helps you see how visitors move through your website, how long they spend on each page, and which pages are more popular than others. Tracking goals helps you track specific actions you would like your visitors to do, e.g., subscribe to your newsletter or click through to your Twitter profile. Actions typically involve clicking a link or a button. When such a click happens, a visitor has done what you wanted them to do, and you can track that you have accomplished your goal for this visitor.
Like any other analytics platform, Fathom requires a custom tracking script to be included in your website. This is straightforward for multipage applications: a visitor loads a page, and the script runs and records the page view. For single-page applications (SPAs), you need to put in additional work to ensure that client-side route changes are also tracked. Fathom lists common integrations in their docs, e.g., for Next.js or Gatsby, but not for SvelteKit.
Package fathom-client
Package fathom-client gives you full
control over triggering Fathom calls at various points in your SPA's page lifecycle. As Matt's
post suggests, src/routes/+layout.svelte
, the root layout component, is the place to
initialize the tracking script:
This code snippet uses Svelte's onMount
callback to load the tracking script
as soon as the layout component has been mounted. Whenever that is the case, the tracking script records
a page view.
Environment variables in SvelteKit
Your Fathom site ID must be exposed to the client for tracking to work. There is no harm in hard-wiring this ID in your code, but it is better to move it into an environment variable as shown here:
The above code snippet uses the module $env/static/public
to expose the variable PUBLIC_FATHOM_SITE_ID
to the client. Note that the environment
variable needs to be prefixed with PUBLIC_
to be exposed to the client.
Dealing with ad-blockers
The tracking script is served from Fathom's domain in the previous code snippets. Since this is a predictable URL, ad-blockers block the tracking script. Fathom used to offer a way to serve the tracking script from a custom domain using a CNAME record. But in May 2023, they advised their customers to stop using custom domains:
You're getting this email because you created a custom domain at some point using Fathom Analytics.
We've had a few reports of custom domains not collecting data due to expired SSL certificates that our vendor isn't renewing automatically, which is unacceptable to us, and we take full responsibility for this.
We've tried to fix this issue with our vendor but aren't getting anywhere, so you need to stop using your Fathom custom domains right now.
The custom domain approach has always been a double-edged sword. On the one hand, as the website owner, you are interested in accurately tracking your visitors. On the other hand, by creating a CNAME record to serve a third-party tracking script, you delegate the reputation of your domain to that third party. If Fathom had gone rogue, they could have served a malicious tracking script from your domain, get access to secrets such as authentication cookies, and destroy trust in your domain. Fathom has always been upfront about this potential issue.
EU isolation
When you use Fathom's default tracking script cdn.usefathom.com/script.js
, any visits
to your website from within the EU will be processed and anonymized within the EU. This is called EU isolation and works out of the box with zero configuration.
Fathom also offers extreme EU isolation, where all global traffic is routed through the
EU. All you need to do is use cdn-eu.usefathom.com/script.js
as tracking script instead
of the default one.
You can provide an options object to Fathom.load
as the second argument. It has a
property url
, which defaults to Fathom's default tracking script. If you want extreme EU
isolation, you need to set the url
option to the alternative tracking script:
Tracking client-side route changes
The previous code snippets track initial page loads. But once a page has been loaded and the JavaScript fully hydrated, SvelteKit switches to client-side routing. Any client-side route changes would not be tracked. To fix this, let's add a reactive statement:
This great hack from Matt's post took me a while to understand. The last line is a reactive
statement, but it is not the typical example where something is assigned to a variable. It uses
JavaScript's comma operator, which evaluates comma-separated operands in sequence and returns the value of the last operand. $page.url.pathname
is a reference to the current path in SvelteKit's page store, and whenever
this value changes, it triggers the reactive statement. The last operand fires a trackPageview
, but only when SvelteKit runs in a browser.
Tracking goals
Let's assume your goal is to make visitors click through to your Twitter profile. The following component can render social icons from an array of objects, which it receives as a prop:
Note the on:click
directive to which an optional callback can be assigned. You can
call Fathom.trackGoal
with a goal tracking ID in this callback:
You have to generate the goal-tracking ID in your Fathom dashboard.