The Playbook unlocked traqqit.com →

Module 7 · ~12 min

📊 Know if it's working — analytics with Umami

You'll leave with privacy-friendly analytics on every app — and one honest number that tells you what to build next.

Ship, then measure

You shipped (Module 6). Now the questions that actually matter: is anyone using it? Which app deserves your next evening? Analytics closes the loop — the data picks your next Module 1. You do not need Google Analytics, a cookie banner, or a marketing suite. You need one honest signal: are real humans showing up, and where.

Why Umami (the lazy, own-your-data choice)

Real fleet setup. The entire traqqit fleet reports to one Umami instance at stats.<domain> — every app, one dashboard, zero cookie banners. It's two small containers on the same box that hosts everything else.

Stand it up (the same pattern as Module 4)

Umami is just two more containers on your web network: the app (a Next.js server) and its own Postgres. No published ports — the shared Caddy fronts it, like every other app.

# a Postgres just for analytics
docker run -d --name stats-db --restart unless-stopped \
  --network web \
  -e POSTGRES_DB=umami -e POSTGRES_USER=umami -e POSTGRES_PASSWORD=CHANGE_ME \
  -v stats_pgdata:/var/lib/postgresql/data \
  postgres:16-alpine

# Umami itself (runs its DB migrations automatically on boot)
docker run -d --name stats --restart unless-stopped \
  --network web \
  -e DATABASE_TYPE=postgresql \
  -e DATABASE_URL=postgresql://umami:CHANGE_ME@stats-db:5432/umami \
  -e APP_SECRET=$(openssl rand -hex 16) \
  ghcr.io/umami-software/umami:postgresql-latest

Then the three moves you already know from Module 4 — a Caddy block, a reload, a DNS record:

stats.yourdomain.com {
    encode gzip
    reverse_proxy stats:3000
}

Reload Caddy (the stdin form from Module 4), then add the stats A record — just ask Claude via the Hostinger MCP, exactly like Module 0: "Add an A record for 'stats' → my VPS IP." Browse to https://stats.yourdomain.com, log in with the default admin / umami, and:

⚠️ Change the default admin password immediately — before anything else. The dashboard sits on a public URL; the default login is public knowledge.

Add your app, grab the one-line snippet

In the Umami dashboard: Settings → Websites → Add website with your app's domain. Umami hands you a data-website-id. Drop this single line into your app's <head>:

<script defer src="https://stats.yourdomain.com/script.js"
        data-website-id="YOUR-WEBSITE-ID"></script>

For a static file-server app (Module 4), that's a one-line edit to index.html + an scp — served live, no rebuild. One website-id per domain; keep a note of which id belongs to which app.

💡 Framework app (Next.js, etc.)? Use its script include — e.g. next/script with strategy="afterInteractive". The src and data-website-id attributes are identical; only the wrapper differs.

Confirm real visits land

Open your app in a normal browser and watch the Umami dashboard — a visit should appear within seconds. If you later script a headless browser to test and see nothing, that's expected, not a bug:

⚠️ Umami filters bots and headless browsers server-side. A default headless-browser load returns 200 but stores nothing. That's correct — real humans are tracked, bots are excluded. Test from a real browser (or override the user-agent) before you panic that "it's not working."

What the dashboard looks like

Once visits are landing, this is roughly what you'll open each morning — clean, fast, and readable at a glance. Pick a time range (today / 7 days / 30 days) and Umami shows the honest picture: how many real people, the trend versus last period, and where they went.

ILLUSTRATION — A TYPICAL UMAMI VIEW FOR ONE APP (EXAMPLE NUMBERS):

📊 stats.yourdomain.com  ·  yourapp.yourdomain.com  ·  Last 7 days
Views1,284▲ 18%
Visitors612▲ 12%
Visits840▲ 9%
Bounce42%▼ 4%

Top pages

/512
/about236
/pricing143
/data88

Illustration of the Umami UI — your real dashboard shows your own live numbers. No cookies, no personal data, no banner.

What to actually look at

Ignore vanity metrics. The honest signals:

That's it. One dashboard, a few honest numbers, no cookie banner, no data broker in the middle.

🦥 Don't build your own analytics. It's tempting to log page views into your own database — resist. Umami is the lazy and correct choice: you own the data, there's nothing to maintain, and you skip the entire cookie-consent circus. One snippet, done.

Optional: one number across the whole fleet

Umami lists sites individually and never sums them. Once you have a few apps, a tiny page can call the Umami API (/api/websites, then /api/websites/{id}/stats) and add them up into a single fleet total. Lazy version: don't build it until you have three or more apps and actually want the aggregate. When you do, it's a static page hitting an API — no new infrastructure.

Your turn ✅

🦥 That's the whole playbook. Seven moves: vet the data, build lazy, static-first, deploy on your box, charge only when it earns it, ship in public, and measure what happens. None is hard alone — the value is running the loop end to end until it's automatic. The numbers you just wired up will hand you your next idea. Go open a dataset.

You finished the playbook. 🎉

Now build something, put it online, and watch the first real visitor land. When it happens, I'd genuinely love to see it — alex@datahoundtech.com. And check back: new material lands here, free, forever.