Analytics for Static Sites Without Trackers — Privacy-First Guide
In this tutorial, you'll learn about Analytics for Static Sites Without Trackers. We cover key concepts, practical examples, and best practices.
Privacy-first analytics for static sites tracks page views, referrers, and user engagement without cookies, fingerprinting, or GDPR consent banners — using server-side logs, lightweight JavaScript beacons, or edge-based measurement instead of traditional tracking scripts.
What You'll Learn
Why It Matters
Google Analytics requires cookie consent banners across the EU, slows page load with a heavy script, and shares visitor data with third-party ad networks. Privacy-first alternatives like Cloudflare Web Analytics and GoatCounter collect essential metrics with zero cookies, no personal data collection, and minimal performance impact — keeping your site fast and legally compliant.
Real-World Use
A documentation site uses Cloudflare Web Analytics to track top pages and referrers without any client-side JavaScript. A developer blog uses GoatCounter for lightweight visit counting with a privacy dashboard. A SaaS landing page uses Plausible for marketing analytics with a clean, real-time dashboard that respects ad blockers.
Analytics Architecture Comparison
flowchart TD
subgraph Traditional[Google Analytics]
A1[User Browser] --> B1[gtag.js Script]
B1 --> C1[Google Servers]
C1 --> D1[Tracking Cookies]
D1 --> E1[Ad Personalization]
end
subgraph Privacy[Privacy-First]
A2[User Browser] --> B2[Edge/Server Log]
B2 --> C2[No Cookies]
B2 --> D2[Anonymous Data]
D2 --> E2[Simple Dashboard]
end
style Traditional fill:#f44,color:#fff
style Privacy fill:#090,color:#fff
Analytics Platform Comparison
| Feature | Google Analytics | Cloudflare Web Analytics | GoatCounter | Plausible | Umami (Self-hosted) |
|---|---|---|---|---|---|
| Cookies | Yes (multiple) | None | None | None | None |
| GDPR compliant | No (needs consent) | Yes | Yes | Yes | Yes |
| Page load impact | ~45KB + requests | ~2KB | ~3KB | ~1KB | ~3KB |
| Blocked by ad blockers | Yes | No | No | Sometimes | No |
| Bounce rate | Yes | Yes | Yes | Yes | Yes |
| Custom events | Yes | No | Limited | Yes | Yes |
| Real-time | Yes | No | Yes | Yes | Yes |
| Self-hostable | No | No | Yes | No | Yes |
| Cost | Free | Free (with CF) | Free (public) / Paid | Paid | Free (self-hosted) |
Cloudflare Web Analytics
Cloudflare Web Analytics measures page views and visits without any client-side JavaScript by using edge log data from Cloudflare's network.
Setup
Enable Web Analytics in the Cloudflare dashboard under Analytics > Web Analytics and add your domain. No code changes are needed if your site is proxied through Cloudflare.
Manual JavaScript Beacon (Optional)
<!-- Cloudflare Web Analytics beacon (deferred, non-blocking) -->
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "your-token"}'>
</script>
Expected behavior: When a visitor loads the page, the beacon sends an anonymous request to Cloudflare's analytics endpoint. The data appears in the Cloudflare dashboard within minutes, showing page views, top pages, referrers, and device types.
GoatCounter Integration
GoatCounter is a lightweight, privacy-first analytics service that can be self-hosted or used via the public hosted version.
JavaScript Snippet
<!-- GoatCounter analytics -->
<script
data-goatcounter="https://your-code.goatcounter.com/count"
async
src="https://gc.zgo.at/count.js">
</script>
Expected behavior: GoatCounter records a page view with the URL, referrer, screen size, and country (from IP address, not stored). No cookies are set, and the data is not shared with third parties. The dashboard shows visit totals, daily breakdowns, and top pages.
Hugo Partial for Conditional Loading
{{/* layouts/partials/analytics.html */}}
{{ if not .Site.IsServer }}
{{ if eq .Site.Params.analytics "goatcounter" }}
<script
data-goatcounter="https://{{ .Site.Params.goatcounterCode }}.goatcounter.com/count"
async
src="https://gc.zgo.at/count.js">
</script>
{{ else if eq .Site.Params.analytics "cloudflare" }}
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "{{ .Site.Params.cloudflareAnalyticsToken }}"}'>
</script>
{{ end }}
{{ end }}
Plausible Analytics Integration
Plausible is a paid, hosted analytics service with a focus on simplicity and privacy.
<!-- Plausible analytics -->
<script
defer
data-domain="yourdomain.com"
src="https://plausible.io/js/script.js">
</script>
Custom Events with Plausible
<button onclick="plausible('Download', {props: {file: 'ebook.pdf'}})">
Download eBook
</button>
Expected behavior: Plausible records the click as a custom event with the file property. The dashboard shows downloads alongside page views, giving you conversion tracking without cookies.
Self-Hosted Umami
Umami is an open-source analytics platform you deploy on your own infrastructure.
# docker-compose.yml — Umami deployment
version: '3'
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://umami:password@db:5432/umami
APP_SECRET: your-secret-key
depends_on:
- db
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: password
volumes:
- umami-data:/var/lib/postgresql/data
volumes:
umami-data:
Umami Tracking Script
<script
defer
src="https://your-umami-instance.com/script.js"
data-website-id="your-site-id">
</script>
Server-Side Log Analytics with Cloudflare
For zero JavaScript, use Cloudflare's edge log analytics:
# wrangler.toml — Enable edge analytics
name = "my-site"
type = "webpack"
[env.production]
workers_dev = false
routes = ["example.com/*"]
[analytics]
enabled = true
Expected behavior: Cloudflare logs every HTTP request at the edge and aggregates the data into analytics dashboards. No JavaScript runs on the client, no cookies are set, and the data includes only what Cloudflare sees: request path, status code, user agent, country, and timing.
Common Errors
1. Analytics Script Blocking Page Load
Synchronous analytics scripts block page rendering. Always add async or defer attributes to analytics scripts. For critical analytics, use Cloudflare Web Analytics which operates at the edge without client scripts.
2. Double-Counting Page Views
If you add analytics to a Hugo partial that renders on both the default template and a custom template, the same page may fire the analytics script twice. Ensure the analytics partial is included only once in baseof.html.
3. Analytics Running in Development Mode
Analytics scripts should not fire during local development. Check if not .Site.IsServer in Hugo templates, or check location.hostname === 'localhost' in JavaScript before initializing analytics.
4. GDPR Compliance with Third-Party Hosting
Even privacy-first tools hosted by third parties (e.g., Plausible Cloud) may require a data processing agreement (DPA) under GDPR. Check each provider's DPA availability and configure data retention periods.
5. Ad Blockers Blocking Self-Hosted Analytics
Some ad blockers use pattern matching to block known analytics endpoints. Host your analytics on a subdomain (e.g., analytics.example.com) and use a reverse proxy to serve the script from your main domain if needed.
Practice Questions
1. What makes Cloudflare Web Analytics different from Google Analytics?
Cloudflare Web Analytics does not use cookies, does not require client-side JavaScript (when used at the edge), and does not collect personal data. It works at the CDN level by analyzing HTTP request logs.
2. Which analytics platforms can be self-hosted?
GoatCounter and Umami can be self-hosted. GoatCounter uses SQLite, while Umami requires PostgreSQL. Both are open source and provide full data ownership.
3. Why should analytics scripts use async or defer attributes?
These attributes prevent the analytics script from blocking page rendering. async loads the script in parallel and executes as soon as it downloads. defer loads in parallel but executes after the HTML is fully parsed.
4. How do you prevent analytics from firing during local development?
In Hugo, wrap the analytics partial with {{ if not .Site.IsServer }}. In client-side JavaScript, check if (location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') before initializing.
5. Challenge: Set up Cloudflare Web Analytics for a Hugo site deployed on Cloudflare Pages. Verify that page views appear in the dashboard. Then add GoatCounter as a secondary analytics tool and compare the numbers from both tools for the same traffic period. Document any discrepancies.
Mini Project: Privacy-First Analytics Dashboard
Build a complete analytics setup for a static site:
- Enable Cloudflare Web Analytics for edge-level measurement
- Add GoatCounter as a lightweight JavaScript-based tracker
- Create a Hugo partial that:
- Conditionally loads analytics only in production
- Supports multiple analytics providers via site config
- Includes a
doNotTrackcheck that respects browser DNT headers
- Build a privacy policy page that documents exactly what data is collected
- Compare the analytics data from both sources over one week
Configuration in hugo.toml:
[params]
analytics = "goatcounter"
goatcounterCode = "dodatech"
cloudflareAnalyticsToken = "your-token"
Verify by publishing the site and checking:
- Cloudflare Web Analytics dashboard shows page views
- GoatCounter dashboard shows matching data
- No cookies are set (verify with browser dev tools)
- The analytics script does not load on localhost
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro