Skip to content
← Projects
2026 ·Feature-complete pre-release · self-hostable ·Featured

seyr

Cookieless, no-PII web analytics with a sub-1KB tracker, a columnar event store, and multi-tenant billing — a self-hostable alternative to Google Analytics.

SvelteKitGoClickHousePostgreSQL

The problem

Site owners want per-site traffic insight without cookie banners, consent friction, or handing visitor data to a third party. seyr delivers fast aggregate dashboards while storing no cookies and no personal data — GDPR/CCPA-friendly by design — for small-to-mid operators and multi-site agencies.

Challenges

Identifying visitors without cookies or PII

With no durable client id and raw IP/UA both counting as PII, the Go ingestor derives visitor_id = hash64(dailySalt | ip | ua | domain), consuming IP and UA transiently to compute geo/device then discarding them. The salt rotates every UTC day so a visitor cannot be linked across days, and folding the domain into the hash blocks cross-site correlation.

High-volume events with instant aggregate reads

Every pageview and event lands in ClickHouse as a partitioned MergeTree using LowCardinality/FixedString encodings and a TTL retention ceiling. A daily AggregatingMergeTree rollup and materialized view pre-aggregate pageviews and uniqState(visitor_id), so unfiltered date ranges skip the raw table entirely.

A sub-1KB tracker that still handles SPAs

The embeddable script is built by esbuild to an IIFE with a build-time size guard, importing only a zod-free config subpath. It uses navigator.sendBeacon with a fetch keepalive fallback, patches history.pushState/replaceState for SPA pageviews, honors DNT, and posts to a neutral /i path whose filename avoids ad-blocker trigger words.

Per-tenant quota enforcement at ingest speed

Beacons must resolve to a tenant and count against a monthly quota without touching Postgres on the hot path. A TTL cache resolves domain→site/org/limit (with negative caching of unknown domains) and an in-memory month-to-date counter — seeded from Postgres, flushed as deltas — keeps limit checks O(1), with soft/block modes for over-limit traffic.

Implementation

Decoupled Go ingest path

POST /i always replies 202 (never leaking which UAs or domains are filtered), runs a two-layer bot filter, validates, resolves the site, records usage, and hands the row to a single-goroutine batcher. The buffer flushes by size or interval and drops-and-counts on saturation, so a slow ClickHouse never stalls request latency.

Parameterized read path with rollup fallback

The dashboard maps a whitelist of filter keys to columns and binds every value as a ClickHouse query parameter — no user input is ever interpolated into SQL. Unfiltered daily ranges are served from the events_daily rollup via uniqMerge, falling back to raw events for hourly/filtered queries with WITH FILL gap-zeroing.

Auth and tenancy in SvelteKit

Sessions store a high-entropy token client-side while the DB persists only its SHA-256 (the raw token is never stored), with sliding renewal and argon2 passwords. Signup atomically creates the user, first org, and owner membership; every query is org-scoped and cross-org access 404s.

Provider-agnostic billing with idempotent callbacks

A PaymentProvider interface fronts an SSLCommerz adapter (cards + bKash/Nagad/Rocket) and a mock adapter for dev. Gateway redirects are handled session-independently and idempotently — a finalized payment short-circuits — before activating the subscription and optionally storing a tokenized card for auto-renew.

Why this stack

Go
Cheap goroutines and channels make the single-owner in-memory batcher and drop-on-saturation design natural for the write-hot beacon endpoint, shipping as a lean static binary.
ClickHouse
A columnar OLAP store so high-volume events compress well and aggregate queries over millions of rows stay fast, with materialized-view rollups for time series.
PostgreSQL
The transactional source of truth for users, orgs, sites, subscriptions, and usage counters — data that needs referential integrity.
SvelteKit
One SSR app covering dashboard, marketing, auth, billing, and the read API, deployed via the Node adapter for self-hosting.
esbuild
Minifies and bundles the tracker to an IIFE under 1KB with a build-time size guard.
Turborepo
A pnpm monorepo so a schema change touches the ClickHouse DDL, the Go insert, and the TypeScript query in one commit.

What it does

  • Cookieless, no-PII tracking with daily-rotating visitor hashing and a sub-1KB SPA-aware script
  • Dashboards for visitors, pageviews, bounce rate, and duration with page/source/country/browser/OS/device breakdowns
  • Custom event tracking with key/value props surfaced as ranked conversions
  • Multi-tenant orgs with roles and public shareable dashboards via token
  • Plan-based monthly event limits enforced at ingest (soft/block modes)
  • SSLCommerz billing with idempotent callbacks, tokenized auto-renew, and bot/AI-scraper filtering

Get in touch

Building something interesting? Let's talk.

Whether it's a hard distributed-systems problem, a platform that needs to scale, or just a second opinion — I'm generally up for it.