Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Analytics

Analytics integration with Amplitude, Google Analytics, session replay, and admin reporting dashboard

Analytics

The platform integrates Amplitude for behavioral analytics with session replay, Google Analytics for traffic tracking, and a custom admin analytics dashboard with sales and order reports.

Architecture

amplitude.ts
google-analytics.tsx
date-range.ts
page.tsx
loading.tsx

Amplitude

The Amplitude integration (lib/analytics/amplitude.ts) provides:

Initialization

export const initAmplitude = () => {
  const apiKey = process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY
  if (!apiKey) {
    console.warn('Amplitude API key not found. Analytics will not be tracked.')
    return
  }

  amplitude.init(apiKey, {
    defaultTracking: {
      sessions: true,
      pageViews: true,
      formInteractions: true,
      fileDownloads: true,
    },
  })

  // Session replay for visual debugging
  amplitude.add(sessionReplayPlugin())
}

Event Tracking

export const trackEvent = (
  eventName: string,
  eventProperties?: Record<string, any>
) => {
  amplitude.track(eventName, eventProperties)
}

User Identification

export const identifyUser = (
  userId: string,
  userProperties?: Record<string, any>
) => {
  amplitude.setUserId(userId)
  if (userProperties) {
    const identifyEvent = new amplitude.Identify()
    Object.entries(userProperties).forEach(([key, value]) => {
      identifyEvent.set(key, value)
    })
    amplitude.identify(identifyEvent)
  }
}

Default Tracking

Amplitude automatically tracks:

  • Sessions -- session start/end with duration
  • Page Views -- every route navigation
  • Form Interactions -- form submissions and field focus
  • File Downloads -- any file download clicks

Session Replay

The @amplitude/plugin-session-replay-browser plugin records user sessions for visual replay and debugging in the Amplitude dashboard.

Google Analytics

The google-analytics.tsx component loads the Google Analytics script and provides the global tracking tag. It is included in the root layout for site-wide tracking.

Admin Analytics Dashboard

The admin analytics page (/admin/analytics) provides:

Sales Reports

  • Revenue over time (daily, weekly, monthly)
  • Order volume trends
  • Average order value

Order Analytics

  • Orders by status breakdown
  • Top-selling products by quantity and revenue
  • Order source tracking

Inventory Turnover & Slow Movers

/admin/analytics/inventory-turnover (Analytics → Turnover & Slow Movers) answers how fast stock is selling and what is sitting still.

  • Overall turnover — how many times the shelf sold through in the window, annualised, plus the days on hand needed to clear current stock at that pace.
  • Per-product table, ranked slowest-first: products holding stock with no sales lead, then the longest days of supply (current stock ÷ the window's daily selling pace). The slow-mover flag fires past 90 days of supply, or immediately for stock that sold nothing.

Both COGS and inventory are valued at the current cost price (the platform keeps no historical inventory snapshots to average against), and — as on the margin page — a missing cost is missing, never zero: uncosted products are excluded from the ratio and value figures and counted against a stated coverage percentage. The velocity columns (units, days of supply) need no cost and work regardless. The arithmetic is in lib/analytics/inventory-turnover.ts; the query in inventory-turnover.server.ts reuses the same sales filter as the margin and orders reports. Read-only, gated on analytics:read.

Retention & Repeat Purchase

/admin/analytics/retention (Analytics → Retention & Repeat Purchase) answers whether buyers come back — as one rate and as a cohort grid.

  • Repeat-purchase rate — the share of distinct buyers who placed two or more orders, plus orders-per-buyer.
  • Cohort retention triangle — buyers grouped by the month they were first seen; each later column is the share of that cohort who ordered again that many months on.

Buyer identity is the signed-in userId when present, otherwise the normalised guest email; there is no Customer foreign key on Order. Orders with neither cannot be tied to a person and are excluded (their count is surfaced) rather than counted as one-time buyers, which would deflate every figure. Two honesty rules the grid depends on: month 0 is the acquisition month and is 100% by definition, and a cell the data cannot see yet is blank, never 0% (a cohort acquired last month has no "three months later" column). The math is in lib/analytics/cohort-retention.ts; the query in cohort-retention.server.ts reuses the same sales filter as the other reports. Read-only, gated on analytics:read.

Attribution (UTM)

/admin/analytics/attribution (Analytics → Attribution) answers where orders come from, by first-touch UTM source, medium, campaign, or referrer.

  • How it's captured: there is no server-side cart, so attribution rides in a cookie. The AttributionTracker client component (mounted in the public layout) records the utm_* params, the external referrer host, and the landing path into a jms_attribution cookie the first time a visitor lands with anything to attribute — a purely direct visit is left uncaptured so a later campaign click can still be the first touch. POST /api/checkout reads that cookie and writes the fields onto the Order (utmSource/utmMedium/utmCampaign/utmTerm/utmContent, referrer, landingPage); parsing never throws, so a bad cookie can't break checkout.
  • How it reports: orders are grouped by the chosen dimension into orders, revenue, and average order value, ranked by revenue. Orders with no captured source are their own Direct / none row (never folded into a campaign), and the headline states the share of orders that carried any UTM source — attribution is only ever as complete as capture, and offline/pre-feature orders are honestly direct.

Capture logic is in lib/analytics/attribution.ts (pure, tested) + attribution.client.ts; the report math is in lib/analytics/utm-report.ts and the query in utm-report.server.ts. Read-only, gated on analytics:read. Migration 20260815120000_add_order_attribution adds the nullable columns (no backfill — existing orders are simply direct).

Date Range Filtering

The lib/analytics/date-range.ts utility provides predefined date ranges:

  • Today, Yesterday
  • Last 7 days, Last 30 days
  • This month, Last month
  • Custom range

Environment Variables

VariableDescription
NEXT_PUBLIC_AMPLITUDE_API_KEYAmplitude browser SDK key
NEXT_PUBLIC_GA_MEASUREMENT_IDGoogle Analytics measurement ID

Analytics initialization is guarded by API key checks. If keys are not configured, tracking silently degrades without errors in the console (except a single warning for Amplitude).

How is this guide?

Edit on GitHub

Last updated on

On this page