TideMeter
Skip to Content
Architecture

Architecture

TideMeter uses a modern, modular architecture designed for privacy, performance, and flexibility.

Overview

TideMeter is organized as a Turborepo monorepo with four packages:

PackagePathPurpose
@tidemeter/webapps/web/Next.js 16 + PayloadCMS 3 application
@tidemeter/trackerpackages/tracker/Lightweight tracking script (~1.5KB gzip)
@tidemeter/analyticspackages/analytics/Analytics data layer (Drizzle ORM)
@tidemeter/uipackages/ui/Shared React UI components

Dual Database Pattern

TideMeter uses a dual database architecture that separates application data from analytics data:

Application Database (PostgreSQL)

Managed by PayloadCMS, this stores:

  • Users and authentication
  • Teams and team memberships
  • Website configurations
  • API keys (hashed with SHA-256, tm_ prefix)
  • Funnels (multi-step conversion tracking)

Analytics Database (PostgreSQL, ClickHouse, or SQLite)

Managed by @tidemeter/analytics, this stores:

  • Page view events and custom events
  • Session data (with bounce detection)
  • Visitor identity links (for user identification)
  • Aggregated statistics

The analytics package uses an adapter pattern, allowing you to choose your storage backend:

// PostgreSQL adapter (default) const repo = new PostgresAnalyticsRepository(db); // ClickHouse adapter (high volume) const repo = new ClickHouseAnalyticsRepository(client);

Both adapters implement the same AnalyticsRepository interface, making them interchangeable.

Event Ingestion Pipeline

When a visitor loads a page with the TideMeter tracker, the following happens:

1. Event Collection

The tracker script sends a POST /api/collect request with:

  • Page URL and title
  • Referrer URL
  • Screen dimensions
  • Browser language
  • Website ID
  • Event name (defaults to pageview)
  • Custom event data (optional)
  • User ID (optional, for visitor identification)

No cookies, no personal data, no IP addresses are included in the request.

2. Event Processing

The processor.ts module:

  • Filters bots using a comprehensive UA regex
  • Parses the User-Agent string for browser, OS, and device type information
  • Generates a visitor ID by hashing (SHA-256) a combination of the IP address, User-Agent, website ID, and a daily rotating salt
  • Generates a session ID using a 30-minute window hash
  • Extracts UTM parameters from the URL
  • Extracts geographic data via optional GeoIP (MaxMind)
  • Handles “identify” events to link anonymous visitors to known user IDs

The daily rotating salt ensures visitor IDs change every day, making cross-day tracking impossible.

3. Event Buffering

The buffer.ts module batches events for efficient database writes:

  • Events are collected in memory
  • Flushed when the buffer reaches 100 events
  • Or when 5 seconds have passed since the last flush
  • Whichever comes first

This reduces database write pressure while keeping data reasonably fresh.

4. Database Storage

The buffered events are written to the analytics database through the configured adapter (PostgreSQL or ClickHouse).

Authentication

TideMeter uses PayloadCMS built-in authentication:

  • Cookie-based sessions using payload-token HTTP-only cookie
  • Login via POST /api/users/login
  • Logout via POST /api/users/logout
  • Session check via GET /api/users/me
  • Route protection via proxy.ts (Next.js 16 pattern)

Note: TideMeter uses proxy.ts, not middleware.ts, for request interception. This is the Next.js 16 pattern.

Dashboard Architecture

The dashboard is a React application using:

  • Server Components for data fetching and initial rendering
  • Client Components for interactive charts and real-time data
  • Recharts for data visualization
  • Tailwind CSS 4 for styling with CSS-first configuration
  • @tidemeter/ui for shared component library

Data Flow Diagram

┌─────────────┐ POST /api/collect ┌──────────────┐ │ Tracker │ ─────────────────────────▶│ Next.js │ │ (~1.5KB) │ │ API Route │ └─────────────┘ └──────┬───────┘ ┌──────▼───────┐ │ Processor │ │ (SHA-256 ID, │ │ UA parsing) │ └──────┬───────┘ ┌──────▼───────┐ │ Buffer │ │ (100 events │ │ or 5s flush)│ └──────┬───────┘ ┌─────────────┼─────────────┐ │ │ │ ┌──────▼──────┐ or ┌───────▼──────┐ │ PostgreSQL │ │ ClickHouse │ │ (default) │ │ (optional) │ └─────────────┘ └──────────────┘