Skip to content

olususus/Chie

Repository files navigation

Chie

Self-hosted, cookie-free, GDPR-aligned web analytics. Drop one <script> tag and get the panels you actually use: traffic, sources, pages, devices, locations, hostnames, and Core Web Vitals.

Built with Next.js 15 (App Router), TypeScript, ClickHouse, Redis, and Tailwind CSS.

Highlights

  • No cookies, no localStorage, no fingerprinting.
  • Daily-rotating session ID derived from a salted hash. Sessions cannot be linked across days.
  • Tiny tracking script (~2 KB), no dependencies, defers loading, honours DNT and Sec-GPC.
  • One snippet, many subdomains. Send data-domain="example.com" on every subdomain and slice traffic by hostname in the dashboard.
  • Plausible-style breakdowns: Channels, Sources, Campaigns, Top Pages, Entry Pages, Exit Pages, Countries, Regions, Cities, Browsers, OS, Devices, Hostnames, UTM Campaigns.
  • Click-to-filter everywhere. Filter state lives in the URL.
  • Page detail drawer with sources, referrers, country split, device split, and a 24-bar hour-of-day distribution per page.
  • 7x24 day-of-week heatmap.
  • Optional Core Web Vitals (LCP, FCP, CLS, INP, TTFB) with web.dev good/needs-improvement/poor bucketing.
  • Public read-only shared links per site.
  • Bearer-token API for /api/stats/*.
  • Customisable branding (title, logo, attribution toggle).
  • Custom events via window.chie(name, props?).

Quick Start

git clone https://github.com/olususus/Chie.git
cd Chie
cp .env.example .env

Edit .env and set at minimum:

  • AUTH_SECRET - a 64-character random string
  • SALT_SECRET - a 32-character random string
  • APP_URL - the public URL of your instance (e.g. https://analytics.example.com)

Then start everything:

docker compose up -d

Open http://localhost:3000 to create the admin account.

Tracking Script

Add this to the <head> of every page you want to track:

<script defer data-domain="example.com" src="https://your-instance.com/t.js"></script>

Optional attributes:

Attribute Description
data-domain Your registered site domain (required)
data-api Proxy the event POST through your own origin
data-exclude Comma-separated path globs to exclude client-side
data-track-perf Set to "true" to collect Core Web Vitals
data-allow-localhost Set to allow tracking on localhost

Custom Events

window.chie("signup", { plan: "pro", source: "landing" });

Up to 20 props per event. Keys are capped at 40 characters, values at 200.

Documentation

Topic Link
Installation docs/install.md
Configuration docs/configure.md
Tracking script docs/script.md
API reference docs/api.md
Tutorial docs/tutorial.md
Security model docs/security.md
Security audit docs/security-audit.md
Migration from TinyTracker docs/migrate-tinytracker.md

Architecture

Browser --> t.js (2 KB, no deps)
              |
              v
        POST /api/event
              |
              v
  +-------------------+       +------------+
  |  Next.js (Node)   | ----> | ClickHouse |
  |  App Router API   |       | (events +  |
  |  + Dashboard UI   |       |  metadata) |
  +-------------------+       +------------+
              |
              v
        +----------+
        |  Redis   |
        | (live,   |
        |  rate,   |
        |  sessions)|
        +----------+
  • ClickHouse holds the events table, materialized views for daily aggregates, and user/site/API key metadata in ReplacingMergeTree tables.
  • Redis powers live visitor counters (HyperLogLog over rolling 5-minute windows), per-IP rate limiting, and same-day session deduplication.
  • Next.js hosts both the ingestion API and the dashboard UI from a single process.

Environment Variables

See docs/configure.md for the full list. Key ones:

Variable Required Description
AUTH_SECRET Yes 64-char secret for JWT signing
SALT_SECRET Yes 32-char secret for session hashing
DATABASE_URL Yes ClickHouse HTTP URL
REDIS_URL Yes Redis connection string
MAX_SITES_PER_USER No Default 50
DATA_RETENTION_MONTHS No Default 24
STORE_CITY No Default false
ALLOW_SIGNUP No Set true for open registration

API

All stats endpoints live under /api/stats/* and accept either:

  • A session cookie (from the dashboard), or
  • An Authorization: Bearer tt_... header (API key), or
  • A ?shared=SLUG parameter (public shared link, read-only)

Endpoints:

  • GET /api/stats/aggregate - totals for a period
  • GET /api/stats/timeseries - time-bucketed series
  • GET /api/stats/breakdown - top N by property
  • GET /api/stats/hourly - 7x24 heatmap data
  • GET /api/stats/perf - Core Web Vitals percentiles
  • GET /api/stats/realtime - current live visitors

All accept ?domain=, ?period=, ?from=, ?to=, and f_* filter params.

Security

  • Passwords hashed with bcrypt (cost 12)
  • Constant-time login (dummy compare on user-not-found)
  • Rate limiting on all auth endpoints
  • Parameterized ClickHouse queries (no string interpolation)
  • Zod validation on every request body
  • Security headers on all dashboard responses (HSTS, X-Frame-Options, nosniff, Permissions-Policy)
  • Tracker sends with credentials: "omit" (no cookies ever)
  • Generic error messages that do not leak user enumeration

Development

npm install
npm run dev

Requires ClickHouse and Redis running locally (or use docker compose for just the backing services).

npm run typecheck   # TypeScript check
npm run lint        # ESLint
npm run build       # Production build

Contributing

  1. Fork the repo
  2. Create a feature branch
  3. Make your changes
  4. Run npm run typecheck and npm run lint
  5. Open a pull request

Please keep PRs focused on a single change.

License

MIT

About

Lightweight analytics built for your website

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages