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
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
AttributionTrackerclient component (mounted in the public layout) records theutm_*params, the external referrer host, and the landing path into ajms_attributioncookie 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/checkoutreads that cookie and writes the fields onto theOrder(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
| Variable | Description |
|---|---|
NEXT_PUBLIC_AMPLITUDE_API_KEY | Amplitude browser SDK key |
NEXT_PUBLIC_GA_MEASUREMENT_ID | Google 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?
Last updated on