The Problem
Web analytics is dominated by Google Analytics, which is privacy-hostile, cookie-dependent, and notoriously complex. Teams that want privacy-friendly, self-hosted analytics often sacrifice depth — Plausible is simple but shallow, and GA4 is powerful but opaque. Rybbit targets the gap: cookieless analytics with session replays, funnels, journeys, and advanced filtering, deployable on your own infrastructure.
What This Does
Rybbit is a full-stack web analytics platform. The client/ directory (696 files) is a Next.js/React application with Tailwind, handling dashboards, funnels, goals, and session replay. The server/ directory (248 files) is an Express/TypeScript API backed by PostgreSQL and ClickHouse. A monitor-agent/ (27 files) handles uptime checks, and shared/ (9 files) holds cross-project types.
The repository is actually a portfolio of four self-contained projects sharing a domain, not a single monolithic codebase. Each has its own package.json and Dockerfile. The README positions it against GA4, Plausible, and Cloudflare, with features like 3-level location tracking and user profiles.
How It Is Wired
Execution starts at monitor-agent/src/index.ts:5 in the start function, which reaches only 2 functions — the agent is a small, isolated component. The main application flow is not mapped from an entry point in this analysis; the framework callbacks (Next.js routes, Express handlers) are invisible to static import graphs.
Everything routes through a handful of hubs. server/src/db/postgres/postgres.ts has 90 modules importing it (instability 0.01 — highly stable, rarely changes), and client/src/api/analytics/endpoints/index.ts has 88 importers but imports 13 modules itself (instability 0.13). The client/src/lib/store (80 importers) and client/src/api/utils.ts (64 importers) are the client-side chokepoints.
The most-called internal functions are cn (97 call sites), authedFetch (37), and buildApiParams (21). The client's authedFetch is the single gateway for all authenticated API calls — changing its signature breaks 37 call sites. Server-side, server/src/db/postgres/postgres.ts and schema.ts (86 importers) are where schema changes ripple widest.
Four modules participate in circular import dependencies, including server/src/services/tracker/utils.ts and client/src/app/[site]/globe/globeStore.ts. These cycles mean a change to one module can trigger subtle initialization-order bugs in the other.
How To Use It
The README documents two paths: hosted service or self-hosting. For self-hosting, the repo includes clickhouse/docker-compose.clickhouse.yml and a Caddyfile for reverse proxying.
git clone https://github.com/moses-y/rybbit
cd rybbit
# Server: npm install && npm run dev (in server/)
# Client: npm install && npm run dev (in client/)
# ClickHouse: docker compose -f clickhouse/docker-compose.clickhouse.yml up -d
Configuration lives in .env.example at the root — copy it to .env and set database credentials, ClickHouse connection, and any auth keys. The exact server start command is not documented in the README excerpt; check server/package.json scripts.
Real-World Use
A typical deployment: a SaaS company embeds the tracker script on its marketing site and app. The client sends events to the server, which writes raw data to ClickHouse for fast aggregation and metadata to PostgreSQL. The team monitors funnels (signup → activation → retention) in the React dashboard, uses session replays for debugging UX issues, and sets up the monitor agent to check uptime across regions. The monitor-agent checks regions via db.query.agentRegions.findMany — the only traced external effect.
Code Health & Issues
Static analysis (not opinion) found 167 findings: 43 high, 123 medium, 1 low. Key items:
- Critical - OAuth token in workflow -
.github/workflows/claude.ymlexposesCLAUDE_CODE_OAUTH_TOKENto contributor-controlled PRs; move toworkflow_runor environment-gated jobs. - High - Unpinned GitHub Actions -
.github/workflowsuses@v1/@v3tags; pin to commit SHAs. - High - Direct push to master -
.github/workflows/translate-docs.ymlrunsgit push origin masterwith no test gate. - High - Duplicated code blocks - 1,815 repeated 6-line blocks across 348 files; DRY the API endpoints.
- High - Deep nesting (14 files) -
docs/.../ComparisonPage.tsxhits indentation depth 7. - Medium - 60.5MB
GeoLite2-City.mmdbin git; move to LFS or object storage. - Medium - 6 test files against 806 source files (ratio 0.007); coverage is nominal.
- Medium - No dependency vulnerability scan in CI.
The Bottom Line
Rybbit is a serious, feature-rich analytics platform with strong architecture in the client and a clean separation between ClickHouse and PostgreSQL concerns. The critical CI secret exposure and the test gap are real blockers for production adoption — fix those before trusting it with traffic data. For a team wanting GA4-level depth with self-hosting, this is worth evaluating; for a team wanting a drop-in simple tracker, Plausible remains lighter.