How to add privacy-friendly analytics to an Astro site
I like knowing which posts people actually read. But I do not want to slow my site down with a heavy tracking script, and I do not want to drop cookies or hand visitor data to an ad network. That is the tension privacy-friendly analytics solves. Here is the tiny component I use on this blog, and everything happening inside it.
Why “privacy-friendly” matters
Traditional analytics tools often set cookies, follow users across sites, and ship large JavaScript bundles that hurt page speed. Privacy-friendly analytics instead count page views without cookies and without personally identifying anyone — so you learn what is popular without surveilling anyone, and usually without needing a cookie-consent banner.
This blog uses Vercel Web Analytics via the @vercel/analytics package. It is aggregate, cookie-free, and lightweight.
The entire component
Here is the real file — all of it:
// blog/src/components/Analytics.astro
---
// Vercel Web Analytics — framework-agnostic injection, mirrors the root
// portfolio's <Analytics /> usage. Runs client-side only.
---
<script>
import { inject } from "@vercel/analytics"
inject()
</script>Yes, that is the whole thing. Let me explain why it is so small and what each piece does.
The empty frontmatter
---
// ...comments only...
---The top fence (between the --- lines) is Astro’s frontmatter — code that runs at build time on the server. Here it is empty except for comments, because this component does no server work at all. Everything it does happens in the browser.
The <script> tag and why it runs client-side
<script>
import { inject } from "@vercel/analytics"
inject()
</script>In Astro, a plain <script> tag is processed and bundled, then sent to the browser — it is client-side JavaScript, not build-time code. That is exactly what we want, because analytics has to run where the visitor is.
import { inject } from "@vercel/analytics"pulls in the analytics helper. Astro bundles this for you; you do not add a<script src="...">tag to some CDN by hand.inject()is the one call that does the work: it sets up the analytics client on the page and starts recording page views.
Because it lives in a <script>, none of this touches the prerendered HTML. The static page loads first; the analytics script initializes afterward. So it never blocks your content from appearing.
flowchart LR A[Static HTML loads] --> B[Browser runs script tag] B --> C["inject() starts analytics"] C --> D[Page views recorded, no cookies]
Using the component
To actually collect data, the component has to appear on every page. The clean way is to render it once inside the shared base layout so every page inherits it:
// inside your BaseLayout.astro
---
import Analytics from "@/components/Analytics.astro"
---
<html>
<body>
<slot />
<Analytics />
</body>
</html>Dropping <Analytics /> in the layout means you never have to remember to add it per page — one import, site-wide coverage.
Getting it running
If you want to reproduce this in your own Astro project:
- Install the package:
bun add @vercel/analytics(ornpm install @vercel/analytics). - Create
src/components/Analytics.astrowith the<script>that imports and callsinject(). - Render
<Analytics />once in your base layout. - Deploy to Vercel and enable Web Analytics for the project in the dashboard — data shows up there, not in your code.
That last step matters: inject() only reports somewhere useful once the project is deployed on Vercel with analytics turned on. Locally it will run harmlessly but you will not see dashboards.
Why put it in a component at all?
You might wonder why not paste the script straight into the layout. Wrapping it in Analytics.astro keeps the layout readable, gives the behavior a clear name, and means if I ever swap analytics providers I change one small file instead of hunting through markup. Small components with one responsibility are easier to reason about — that is the same instinct behind the FormattedDate component elsewhere on this blog.
What to remember
- Privacy-friendly analytics count views without cookies or personal tracking — often no consent banner needed.
- A plain Astro
<script>tag is client-side code; that is where analytics belongs. - The whole integration is
import { inject }plusinject()— Astro bundles the package for you. - Render
<Analytics />once in your base layout for automatic site-wide coverage. - On Vercel, enable Web Analytics in the dashboard for the data to actually land.
Related reading: Giscus comments is another small, privacy-conscious client-side add-on wired up the same way.