Scott Spence

Scott's Digital Garden.

Add Analytics Tracking Links to your Markdown

I'm talking Fathom Analytics again, this time about tracking link clicks in my Markdown.

I have written about how to track custom events with Fathom previously and this is really an extension to that post.

If you would like to try Fathom and get a £10 discount on your first invoice check my affiliate link.

This situation is specific to using MDX and the MDXProvider as it gives the ability to override rendering of links.

The problem 🤔

I'd like to add a Fathom goal to a link in my Markdown, to do this I'll need to get the goal ID I've created in Fathom to the link so that when it's clicked it's logged with Fathom.

So I can add an onClick event to the a tag using the MDXProvider and pass that ID to the analytics provider.

The solution 💡

Ok, so I need a way to add a prop to a specific link that contains the goal ID for Fathom.

Using a made up attribute added to the markup for the goal ID I can pick out the ID and pass it to the analytics provider from the A component I'm using to override the links in the Markdown.

1export const A = props => {
2 const fa = useAnalytics()
3 return (
4 <a
5 {...props}
6 id={props.id}
7 onClick={() => fa(props.goal)}
8 target="_blank"
9 rel="noopener noreferrer"
10 >
11 {props.children}
12 </a>
13 )
14}

So the Markdown will look something like this:

1# My awesome Markdown
2
3Fathom Analytics recently added a feature for
4<a href="https://usefathom.com/blog/bypass-adblockers" goal="IBQBRDPP">custom
5domains</a> with their service.

So, job done right?

Wellllll....

No.

The other problem (for me anyway) 😧

I spend a lot of my time in Markdown files and I rarely use inline links. With an inline Markdown link the link from that last example it would look something like this:

1# My awesome Markdown
2
3Fathom Analytics recently added a feature for
4[custom domains](https://usefathom.com/blog/bypass-adblockers) with
5their service.

Doesn't read great does it?

When there is a lot of prose (text) it can be difficult to read when there are a lot of links in there.

I prefer to use the link text and reference it at the bottom of my document, like this:

1# My awesome Markdown
2
3Fathom Analytics recently added a feature for [custom domains] with
4their service.
5
6<!-- Links -->
7
8[custom domains]: https://usefathom.com/blog/bypass-adblockers

The other solution 🤯

So, I'd prefer to have the goal ID in the link but not cluttering up my beautifully crafted Markdown with a tags everywhere. 😁

How do I do that then?

The way I worked out how to do it was to use a URL parameter and intercept it in the A component.

In the Markdown I add the goal ID to the URL, on line 9:

1# My awesome Markdown
2
3Fathom Analytics recently added a feature for [custom domains] with
4their service.
5
6<!-- Links -->
7
8[custom domains]:
9 https://usefathom.com/blog/bypass-adblockers?goalId="IBQBRDPP"

This way the structure of the Markdown is unchanged making a much nicer writing experience.

How to use the goal ID from the URL parameter?

First up I used an onClick handler to pull out the goalId for the analytics provider.

Using the URL(props.href).searchParams means that I can pull out the goalId using the .get function.

1export const A = props => {
2 const fa = useAnalytics()
3 const onClick = () => {
4 if (props.href.includes(`goalId`)) {
5 const params = new URL(props.href).searchParams
6 fa(params.get(`goalId`))
7 }
8 }
9
10 return (
11 <a {...props} onClick={onClick}>
12 {props.children}
13 </a>
14 )
15}

The only thing with this was that the goalId would still be in the URL.

url with goalid showing

I didn't really like the way this looked, especially if someone was going to share that link after clicking it.

So reading into the URL API a bit more and reading up on some StackOverflow posts I found that I could delete the parameter after using it.

In the next block of code here, on line 3, I'm checking if the props.href contains a goalId parameter.

I can then manipulate the props.href in useEffect and setting the goalId in state first on line 10.

Then I can delete the goalId from the url on line 11.

The props have been changed now so I'll need to provide the new href to the a tag in the props, on line 25.

1export const A = props => {
2 const fa = useAnalytics()
3 const containsGoalId = props.href.includes(`goalId`)
4 const [goalId, setGoalId] = useState(``)
5 const [newHref, setNewHref] = useState(``)
6
7 useEffect(() => {
8 if (containsGoalId) {
9 const url = new URL(props.href)
10 setGoalId(url.searchParams.get(`goalId`))
11 url.searchParams.delete(`goalId`)
12 setNewHref(url.href)
13 }
14 }, [containsGoalId, props.href])
15
16 const onClick = () => {
17 if (goalId) {
18 fa(goalId)
19 }
20 }
21
22 return (
23 <a
24 {...props}
25 href={containsGoalId ? newHref : props.href}
26 onClick={onClick}
27 >
28 {props.children}
29 </a>
30 )
31}

Now clicking the link will only call the analytics provider when there's a goal ID in the href passed to the component in the props.

Here's a video 📺

In this video detail what I've been writing about.

Resources 📑

These resources helped me along the way.