An unofficial Progressive Web App (PWA) for GoatCounter
analytics. Track your website analytics on the go with a mobile-friendly
interface that works against the official goatcounter.com SaaS, self-hosted
GoatCounter instances, and custom domains.
- Real-time analytics from multiple GoatCounter sites in one place
- Trend chart of visitors over the selected range, built from the
/stats/totaltime-series (hourly buckets for sub-day ranges, daily otherwise) - Top pages with referrer drill-down — tap any page to see where its visits came from
- Browser + OS breakdowns
- Visitor map with country flags and color-coded intensity bars
- Flexible date ranges: hourly (1H, 6H, 12H) and daily (1D, 7D, 30D, all-time)
- Adaptive rate limiting — respects GoatCounter's
X-Rate-Limit-*response headers, so it works correctly under any rate-limit configuration without code changes - Self-hosted & custom-domain support — enter a full URL like
https://stats.example.com; bare codes likemyblogare also accepted and expanded tomyblog.goatcounter.com - Offline support with smart caching
- PWA: installable on mobile and desktop
- Android APK companion — distributed via GitHub Releases for Obtainium pickup (see docs/ANDROID.md)
A free hosted version may be available; for production use, please self-host or fork.
- Node.js 18 or higher
- pnpm (recommended) —
npm install -g pnpm - A GoatCounter account with API access
git clone https://github.com/serrodneyrich/Shepherd.git
cd Shepherd
pnpm install
pnpm devOpen http://localhost:3000.
- Open the app and navigate to Settings.
- Enter your GoatCounter API key.
- GoatCounter dashboard → click @Username (top-right) → Settings → API
- Generate an API key with Read Sites and Read Statistics permissions only
⚠️ Never use a key with write permissions — the API key is stored in browser localStorage and is visible to anyone with device access
- Click Save & Validate API Key. If your GoatCounter instance is v2.7 or
newer, Shepherd verifies the key against
/api/v0/meand shows a green checkmark with your account email. Older instances skip validation (the key is still saved). - Add your sites. The "Site URL" field accepts:
- Full URLs for self-hosted GoatCounter or custom domains:
https://stats.example.com - Bare codes (legacy, automatic):
myblog→https://myblog.goatcounter.com
- Full URLs for self-hosted GoatCounter or custom domains:
NEXT_PUBLIC_* variables are embedded in the client
bundle and visible to all visitors.
echo "NEXT_PUBLIC_GOATCOUNTER_API_KEY=your_read_only_api_key" > .env.localUse read-only API keys only.
Shepherd reads GoatCounter's X-Rate-Limit-Remaining and X-Rate-Limit-Reset
response headers and paces requests adaptively. There is no fixed delay — when
the budget runs low, Shepherd waits until the window refills; when there's
plenty of budget, requests fire as fast as needed (with a 100ms floor between
consecutive requests to avoid bursts).
This means Shepherd correctly handles GoatCounter's:
- v2.5 and earlier: 4 req / 10 sec
- v2.6.0+: 4 req / sec
- Future increases: handled automatically with no code change
- Self-hosted with custom
-ratelimit: also handled automatically
On 429, Shepherd backs off using X-Rate-Limit-Reset and retries up to 3
times. On 5xx, it retries with exponential backoff and jitter.
Rate-limit state is shared across browser tabs via BroadcastChannel so multiple
open tabs never together exceed the budget.
- 5-minute in-memory cache for API responses (LRU, max 100 entries)
- In-flight request deduplication: simultaneous requests for the same data share a single network call
- Service-worker
NetworkFirstcache for offline support (5-minute TTL) - Per-site, per-range cache layered on top of all the above, configurable via the auto-fetch interval setting
The repo ships a companion Android APK built via
Bubblewrap (Trusted Web
Activity). Tagging a release on GitHub triggers a CI build that signs and
attaches shepherd-X.Y.Z.apk to the GitHub Release.
To install via Obtainium:
- Open Obtainium → Add App
- URL:
https://github.com/serrodneyrich/Shepherd - Source: GitHub
- Obtainium picks up the latest
.apkand offers updates as new releases tag.
For maintainer setup (keystore, GitHub Secrets, assetlinks), see docs/ANDROID.md.
Shepherd is a client-side PWA — all code runs in your browser, API calls go directly from your device to GoatCounter, no Shepherd backend is involved.
- Use read-only API keys (Read Sites + Read Statistics only)
- Never share API keys
- Use only on trusted devices
- Self-host for production
- API keys are stored in browser localStorage (unencrypted)
- Anyone with device or DevTools access can read the key
- This is inherent to client-side PWA architecture
Do not open a public GitHub issue. Use GitHub's private security advisory feature or email the maintainer.
PRs welcome. Run pnpm lint before submitting. The dashboard styling is
intentionally minimal and dark-themed — if you see things you'd improve in
the GoatCounter web UI itself, those PRs probably belong upstream
(github.com/arp242/goatcounter).
MIT.
- GoatCounter — the privacy-friendly analytics platform Shepherd is built for. Thanks to Martin Tournoij for the helpful feedback that shaped this revision.
- The open-source community for the tools and libraries.
Unofficial third-party application. Not affiliated with or endorsed by GoatCounter.