AuthonAuthon Blog
comparison6 min read

Migrating Off Google Analytics: Umami vs Plausible vs Fathom

A hands-on comparison of Umami, Plausible, and Fathom as privacy-focused alternatives to Google Analytics, with migration steps and honest tradeoffs.

AW
Alan West
Authon Team
Migrating Off Google Analytics: Umami vs Plausible vs Fathom

The wake-up call I didn't ask for

Last week the TanStack folks reported what appears to be a compromise affecting some of their NPM packages (the details are still being sorted out in issue #7383 — read it yourself before drawing conclusions). I won't rehash the postmortem here. What I want to talk about is the gut-punch feeling I had reading it.

I run npm install every day. I've barely thought about which third-party scripts are loading in production. And one of the worst offenders sitting in nearly every site I've ever shipped? Analytics.

So this post is about something I've been chewing on for months but finally moved on: ripping Google Analytics out of three side projects and picking a privacy-focused alternative. Specifically, I'll compare Umami, Plausible, and Fathom — the three I actually evaluated — and walk through the migration steps that worked for me.

Why even migrate?

A few honest reasons, none of them ideological:

  • Script weight. GA4's gtag.js is heavy. The privacy-focused tools are typically 1–2 KB.
  • Cookie banners. No cookies = no consent banner in most jurisdictions. Fewer modals = fewer bounces.
  • Vendor trust. After watching a supply chain story unfold in real time, having fewer third-party scripts feels less reckless.
  • Self-hosting option. If I can run it on my own infra, I control the script.

If you genuinely need Google's audience features (remarketing, conversion linking to Google Ads), this post probably isn't for you. Stay where you are.

The contenders

Plausible

Open source (AGPL), GDPR/CCPA compliant, cloud or self-hosted. The script is small — the docs claim under 1 KB. Written in Elixir. Cloud plans are subscription-based.

Fathom

Privacy-focused, cloud-only since they pivoted from the original open source v1 ("Fathom Lite," archived) to a commercial closed-source product. I evaluated the commercial product.

Umami

Open source (MIT), self-hosted by default with a hosted cloud option on umami.is. Built on Next.js, runs on PostgreSQL or MySQL. Free if you host it yourself. Easy enough that I had it running in an evening.

Side-by-side

I'll keep this honest — I ran all three on the same site for two weeks before deciding.

| Feature | Plausible | Fathom | Umami |
|---|---|---|---|
| Open source | Yes (AGPL) | No (closed) | Yes (MIT) |
| Self-host | Yes | No | Yes (primary path) |
| Cookies | No | No | No |
| GDPR | Yes | Yes | Yes |
| Cloud option | Paid | Paid | Free tier + paid |
| Script size | ~1 KB | ~2 KB | ~2 KB |
| Funnels / goals | Yes | Yes | Yes (basic) |

The sizes above match what I observed in the network tab, but check each vendor's docs before quoting them anywhere serious.

What the snippets look like

Replacing GA is mostly about swapping a script tag. Here's the before:

html
<!-- Google Analytics (the thing we're leaving) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXX'); // sends pageview + sets cookies
</script>

And the replacements:

html
<!-- Plausible (cloud) -->
<script defer data-domain="example.com"
        src="https://plausible.io/js/script.js"></script>

<!-- Fathom -->
<script src="https://cdn.usefathom.com/script.js"
        data-site="ABCDEFG" defer></script>

<!-- Umami (self-hosted) -->
<script defer src="https://analytics.mydomain.com/script.js"
        data-website-id="your-website-id"></script>

That's it. No dataLayer. No consent banner gate. The script loads once, sends a single beacon per pageview, and stops bothering you.

Custom events

The thing I almost forgot when migrating: GA's gtag('event', ...) calls. Here's how I rewrote them for Umami (the APIs are similar across the three, but each has its own conventions):

js
// Before (GA4)
gtag('event', 'signup_completed', {
  plan: 'pro',
  source: 'pricing_page'
});

// After (Umami)
// `umami` is attached to window by the loader script
window.umami?.track('signup_completed', {
  plan: 'pro',
  source: 'pricing_page'
});

Plausible uses window.plausible('signup_completed', { props: { plan: 'pro' } }). Fathom uses fathom.trackEvent('signup_completed'). Don't do a global find-and-replace — the property conventions differ enough that you'll want to read each vendor's docs first.

Self-hosting Umami in five minutes

This is the part that sold me. Here's the docker-compose.yml running on the VPS for one of my side projects:

yaml
services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:umami@db:5432/umami
      DATABASE_TYPE: postgresql
      APP_SECRET: change-me-to-a-real-secret # rotate this
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: umami
    volumes:
      - umami-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U umami"]

volumes:
  umami-db:

Run it behind Caddy or Nginx, point a subdomain at it, drop the script tag into your site. You own the data. Nothing leaves your server. The dashboard is genuinely pleasant — the Next.js UI loads fast and shows the things I actually look at.

Migration steps that worked

No magic, just mechanical:

  • Inventory your GA calls. Grep your codebase for gtag(, dataLayer, and any analytics wrapper functions. Write them down.
  • Pick your destination. Zero ongoing cost and own your data → self-hosted Umami. Don't want to run Postgres → Plausible Cloud. Want the most polished commercial dashboard → Fathom.
  • Run them in parallel for a week. Drop the new script alongside GA. Compare daily pageview counts. You'll see drift — the privacy-focused tools usually report fewer visits because they don't fingerprint, and that's kind of the point.
  • Rewrite custom events. Map each gtag('event', ...) to the new API. Wrap them in a helper so you can switch again later without grepping.
  • Remove the GA script and the cookie banner. This is the satisfying part.
  • My recommendation

    Honestly? Here's how I'd choose:

    • Side projects, solo devs: Self-hosted Umami. Free, simple, MIT-licensed.
    • Small business, no ops appetite: Plausible Cloud. Easiest onboarding, still open source if you ever want to migrate off.
    • Polished dashboards for clients: Fathom. The UX feels the most "finished" of the three.

    I'm not saying Google Analytics is bad — it's free, it's powerful, and it's still the right answer if you live inside their ad ecosystem. But for the rest of us, three lines of script and a Postgres container will get you 90% of what you actually look at, with one less third-party domain in your Content-Security-Policy.

    The TanStack situation reminded me that every script tag is a trust decision. Make fewer trust decisions.

    Migrating Off Google Analytics: Umami vs Plausible vs Fathom | Authon Blog