Roll Your Own Analytics With Astro, Vercel Edge Functions and Neon
In this post, I’ll explain how you can “roll your own” version of Google Analytics using Astro, Vercel Edge Functions and Neon.
I’ve prepared a sample repository showing the implementation for a site built using Astro and deployed to Vercel.
For good measure, I’ve also created a Next.js sample repository, but I won’t be covering that in this post.
The Google Analytics API
You might be wondering, why not just use the Google Analytics API?
For quite a while, that was my approach, but as you probably know, Google retired Universal Analytics in June 2023. I’d been running GA4 in my site since they announced their plans last year so I had data from both, but there’s a problem.
On my dashboard I have a 3D globe which I use to plot the geographic locations of visitors to my site. In Universal Analytics this was no problem, I could query the API which contained a reporting dimension for the
longitude. In GA4 however, Google have removed this dimension which meant, unless I came up with a solution, I’d have to lose the 3D globe!
The Anatomy of Edge Analytics
This was the solution I came up with and there are three main parts to building your own analytics. They are as follows.
- A Neon serverless Postgres database to store the data.
- An Edge Function capable of extracting geolocation data from incoming requests.
- A client-side request to the Edge Function that fires on page load.
This solution isn’t intended to replace Google Analytics but, it is a nice way to capture site visits and visualise activity on my site.
Create a Neon serverless Postgres database
Once you have a database set up, save the connection string to your
.env file and name it,
Create a table for the data
With your database created, head over to the SQL Editor in the Neon console and use the following schema to create a new table called
There are three dependencies required. The first is the Neon serverless driver, the second is the Astro Vercel adapter, and the third is the @vercel/edge package including a
geolocation helper function that can extract geographical information from incoming requests.
Configure the Astro Vercel Adapter
astro.config.mjs and add the following config.
Create an Edge Function
With the Vercel Adapter set up you can now create a new API route under
src/pages/api. In the sample repository, I’ve named the route page-view.js.
The Edge Function explained
The Edge Function destructures a
slug from the
request.body, this is sent from the client (I’ll cover that in a later step). The Edge Function also creates a new
date when a request is received, this date will be accurate to the user’s timezone since Edge Functions execute in the same timezone as the user.
I then extract the following values from the request using the geolocation helper function.
If any of the above values are
null, or a
slug hasn’t been passed to the Edge Function, I return a message.
If all values are present and correct I proceed to
INSERT INTO the database.
It’s worth noting that all geolocation values will be null while running the development server. You’ll have to deploy to Vercel to see any values from incoming requests.
That completes the database and server-side part of this post. It’s now time to move on to the front end.
Create an EdgeAnalytics component
As Ward mentions in our conversation, using fetch won’t necessarily harm performance, but sendBeacon is pretty much specifically for use with requests of this nature, and to quote from the MDN docs verbatim.
It’s intended to be used for sending analytics data to a web server, and avoids some of the problems with legacy techniques for sending analytics, such as the use of XMLHttpRequest.https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon
It’s worth noting though, some Ad Blockers might block requests made using sendBeacon.
Create a new component in
src/components. In the sample repository, I’ve named the component edge-analytics.astro.
The EdgeAnalytics component explained
The EdgeAnalytics component is a web component with custom elements and contains a self-invoking function that uses
slug to the Edge Function. The slug will be passed as a prop, which I’ll cover in the implementation steps next.
Implement the EdgeAnalytics component
EdgeAnalytics component to each page where you’d like to track page views. You can see how I’ve implemented the component in the sample repository here: index.astro.
EdgeAnalytics component implementation explained
To prevent page views from being fired off while in development, I’m conditionally rendering the component using a ternary operator and an
isProduction const. Only when the environment variable,
true will the component be rendered to a page.
Finally, as I mentioned, the component accepts a prop called
slug which is where I pass in the current page URL.
That completes the front-end part of this post, but to see if everything is working as expected you’ll need to deploy your site and visit a few of the pages.
Once you’ve done that you can head back to the Neon console and run the following against the
If everything is working correctly you should see some new rows of data in the table. Here’s what mine looks like. (I’m in Montreal, Canada FYI)
Data Visualisation Examples
Here are a few examples of what you could do with the data. I’ve implemented some of these on my dashboard, but I’d be interested to see how you’re using this approach.
Feel free to find me on Twitter/X and let me know: @PaulieScanlon.
Top Ten Countries
In this example, I’ve created a query that counts the occurrences of the country name and limits the response to 10.
I’m displaying the results in a simple HTML list.
Top Ten Cities
In this example, I’ve created a query that counts the occurrences of the city name and limits the response to 10.
As before, I’m displaying the results in a simple HTML list.
In this example, I’ve created a query that counts the occurrences of the city name for site visits in the last 30 days.
I’m using the results here to plot the latitude and longitude around a 3D globe and use the total as an altitude for each point. (The taller the point, the more visits from that city).
All Site Visits
In this example, I’ve created a query that counts the occurrences of the day for site visits in the last 30 days.
I’m using the result here to plot an Svg polyline chart to compare total visits over days of the month.
No doubt there are many other ways to represent this data and I might add more features to my site in due course but, for now at least, I’m happy with the way I’m capturing site visits.
Moreover, should Google remove any further reporting dimensions from GA4, I won’t need to change anything!
This data is mine and is securely and reliably stored in a Neon database, ready to be queried however I please, forever more.
If you want to try this out yourself sign up at neon.tech.
Thanks for reading.