Why We Built Xine Analytics

Melvin Prince
4 min read

When we started building out our web infrastructure, we ran into a massive problem. Google Analytics was too heavy, blocked by ad-blockers, and most importantly, it was invasive. Every alternative we evaluated either cost a fortune, required cookies, or locked our data into a third-party SaaS.

We wanted an analytics tool that was developer-friendly, self-hostable, and completely respected user privacy. We could not find one that fit our exact needs, so we built Xine Analytics.

The Problem with Existing Solutions

We evaluated over a dozen analytics platforms. Here is what we found:

| Platform | Issue | |----------|-------| | Google Analytics | Heavy script (~45KB), requires cookies, sends data to Google | | Mixpanel | Expensive at scale, requires cookies for user identification | | Amplitude | Enterprise pricing, complex SDK, cookie-dependent | | Plausible | Good privacy model, but cloud-only or expensive self-hosting | | Matomo | Bloated PHP codebase, slow dashboard, complex deployment |

None of them offered what we wanted: a modern, lightweight, Next.js-native analytics platform that could self-host on a $5 VPS with a premium dashboard experience.

The Problem with Cookies

Privacy laws are becoming stricter, and for good reason. Users deserve to know what data is being collected and how it is being used. The GDPR's ePrivacy Directive, California's CCPA, and Brazil's LGPD all regulate cookie-based tracking.

By dropping cookies entirely, we bypassed the need for annoying cookie banners while still getting accurate unique visitor counts. Our approach uses anonymized identification through localStorage—which is not a cookie and does not fall under cookie consent requirements when used for essential functionality.

How Our Cookie-Free Tracking Works

  1. First visit: Generate a random UUID in localStorage
  2. Subsequent visits: Reuse the stored UUID for consistent visitor counting
  3. Session tracking: Use sessionStorage (cleared when the tab closes) for session identification
  4. Fallback: If localStorage is disabled, use server-side heuristics (IP + User-Agent hash with daily rotation)

This gives us accurate visitor and session metrics without ever setting a document.cookie.

Why PostgreSQL?

We chose PostgreSQL (via Drizzle ORM) because it is battle-tested, highly scalable, and supports powerful analytical queries via JSONB columns. Our schema is designed for analytical workloads:

// Sample Xine Query Structure
const data = await db.select({
  pageviews: sql<number>`count(*)`,
  visitors: sql<number>`count(distinct ${pageviews.visitorId})`
}).from(pageviews)
.where(
  and(
    eq(pageviews.siteId, siteId),
    gte(pageviews.timestamp, startDate),
    lte(pageviews.timestamp, endDate)
  )
);

PostgreSQL's count(distinct) is remarkably efficient for our use case, and Drizzle ORM gives us type-safe queries without the overhead of a traditional ORM like Prisma.

Why Not ClickHouse or TimescaleDB?

We considered columnar databases optimized for analytics, but PostgreSQL won for several reasons:

  • Simpler deployment: Single database for both the application and analytics data
  • Familiar tooling: Every developer knows PostgreSQL
  • Good enough performance: For sites under 10M pageviews/month, PostgreSQL handles analytical queries effortlessly
  • Drizzle ORM ecosystem: Type-safe queries, automatic migrations, and excellent DX

Why Next.js?

We built Xine on Next.js 16 with the App Router because:

  • Server Components let us render the dashboard without sending data-fetching logic to the client
  • API Routes provide a clean, co-located API for both the tracking endpoint and dashboard data
  • Edge Runtime compatibility for our authentication middleware
  • Static generation for the tracking script (t.js) served from /public

The 8KB Tracking Script

One of our proudest technical achievements is the tracking script. At just 8KB minified, t.js packs an incredible amount of functionality:

  • Pageview tracking with SPA support (History API detection)
  • Custom event tracking
  • Core Web Vitals (LCP, FCP, CLS, INP, TTFB)
  • Scroll depth monitoring
  • Outbound click tracking
  • File download tracking
  • Rage click detection
  • JavaScript error tracking
  • Form abandonment tracking
  • Session replay (opt-in)

All of this loads asynchronously and never blocks the main thread.

What Is Next

Xine v2.0 introduces real-time Web Vitals tracking, session replays, and a complete funnel builder. We are also working on managed infrastructure so you can deploy Xine with one click.

We are just getting started.

Want to try it yourself? Get started with Xine →

All articles

Published by Melvin Prince at Unisource