Amplitude Analytics
"Amplitude: product analytics, behavioral cohorts, funnels, retention, experiment platform, user journeys, JavaScript SDK"
Amplitude is a product analytics platform focused on understanding user behavior at scale. Its core model revolves around events, users, and properties, enabling teams to build funnels, retention analyses, behavioral cohorts, and user journey maps. Amplitude encourages an "instrument once, analyze forever" approach where a well-designed taxonomy of events and ## Key Points - **Define a typed event taxonomy** using TypeScript interfaces. A type-safe tracking function - **Use `Identify.setOnce()` for immutable properties** like `first_login_date` or - **Use `Identify.add()` for counters** like `login_count` or `feature_usage_count` instead - **Call `amplitude.reset()` on logout** to clear the user ID, device ID, and all user - **Flush events in serverless environments** by calling `client.flush()` after tracking to - **Enable `automaticExposureTracking`** in the Experiment SDK so that variant assignments - **Use the Revenue class** for all monetary tracking instead of custom events with amount - **Sample session replays** to control costs. A 5-10% sample rate is usually sufficient to - **Creating unique event names per page or feature.** Events like `home_page_button_click` and - **Sending high-cardinality string properties.** Properties like `search_query` or `user_agent` - **Calling `amplitude.init()` on the server.** The browser SDK must only run client-side. Use - **Tracking events before identifying the user.** Events tracked before `setUserId` are
skilldb get analytics-services-skills/Amplitude AnalyticsFull skill: 377 linesAmplitude Analytics
Core Philosophy
Amplitude is a product analytics platform focused on understanding user behavior at scale. Its core model revolves around events, users, and properties, enabling teams to build funnels, retention analyses, behavioral cohorts, and user journey maps. Amplitude encourages an "instrument once, analyze forever" approach where a well-designed taxonomy of events and properties powers unlimited ad-hoc queries without re-instrumentation. The platform also includes an experimentation layer for A/B testing and a customer data platform (CDP) for syncing behavioral data across tools. The key principle is that analytics should answer "why" users convert or churn, not just report "how many."
Setup
Browser SDK Installation
// lib/amplitude.ts
import * as amplitude from "@amplitude/analytics-browser";
import { sessionReplayPlugin } from "@amplitude/plugin-session-replay-browser";
export function initAmplitude(): void {
amplitude.init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!, {
autocapture: {
elementInteractions: true,
pageViews: true,
sessions: true,
},
defaultTracking: {
pageViews: true,
sessions: true,
formInteractions: true,
fileDownloads: true,
},
minIdLength: 1,
flushIntervalMillis: 1000,
flushQueueSize: 30,
logLevel: amplitude.Types.LogLevel.Warn,
});
// Add session replay plugin
const sessionReplay = sessionReplayPlugin({
sampleRate: 0.1, // Record 10% of sessions
});
amplitude.add(sessionReplay);
}
export { amplitude };
Next.js Provider
// app/providers.tsx
"use client";
import { useEffect } from "react";
import { initAmplitude } from "@/lib/amplitude";
export function AmplitudeProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
initAmplitude();
}, []);
return <>{children}</>;
}
Node.js Server SDK
// lib/amplitude-server.ts
import { Amplitude } from "@amplitude/analytics-node";
let serverClient: Amplitude | null = null;
export function getAmplitudeServer(): Amplitude {
if (!serverClient) {
serverClient = new Amplitude(process.env.AMPLITUDE_API_KEY!);
}
return serverClient;
}
Key Techniques
Event Tracking with Typed Events
// lib/analytics-events.ts
import { amplitude } from "@/lib/amplitude";
import { Identify } from "@amplitude/analytics-browser";
// Type-safe event definitions
interface AnalyticsEvents {
signup_started: { method: string; source: string };
signup_completed: { method: string; time_to_complete_ms: number };
feature_used: { feature: string; context: string };
upgrade_clicked: { current_plan: string; target_plan: string; source: string };
search_performed: { query_length: number; result_count: number; filters: string[] };
}
export function trackEvent<K extends keyof AnalyticsEvents>(
event: K,
properties: AnalyticsEvents[K]
): void {
amplitude.track(event, properties);
}
// Usage
function onSignupComplete(method: string, startTime: number): void {
trackEvent("signup_completed", {
method,
time_to_complete_ms: Date.now() - startTime,
});
}
User Identity and Properties
import { amplitude } from "@/lib/amplitude";
import { Identify } from "@amplitude/analytics-browser";
// Identify user after authentication
function identifyUser(user: {
id: string;
email: string;
plan: string;
companyId: string;
role: string;
}): void {
amplitude.setUserId(user.id);
const identify = new Identify();
identify.set("email", user.email);
identify.set("plan", user.plan);
identify.set("company_id", user.companyId);
identify.set("role", user.role);
identify.setOnce("first_login_date", new Date().toISOString());
identify.add("login_count", 1);
amplitude.identify(identify);
}
// Set group for B2B analytics
function setUserGroup(companyId: string, companyName: string): void {
amplitude.setGroup("company", companyId);
const groupIdentify = new Identify();
groupIdentify.set("name", companyName);
groupIdentify.set("updated_at", new Date().toISOString());
amplitude.groupIdentify("company", companyId, groupIdentify);
}
// Reset on logout
function handleLogout(): void {
amplitude.track("user_logged_out");
amplitude.reset();
}
Revenue Tracking
import { amplitude } from "@/lib/amplitude";
import { Revenue } from "@amplitude/analytics-browser";
function trackPurchase(order: {
productId: string;
productName: string;
price: number;
quantity: number;
}): void {
const revenue = new Revenue();
revenue.setProductId(order.productId);
revenue.setPrice(order.price);
revenue.setQuantity(order.quantity);
revenue.setRevenueType("purchase");
revenue.setEventProperties({
product_name: order.productName,
currency: "USD",
});
amplitude.revenue(revenue);
}
function trackSubscription(plan: string, mrr: number, interval: string): void {
const revenue = new Revenue();
revenue.setProductId(plan);
revenue.setPrice(mrr);
revenue.setQuantity(1);
revenue.setRevenueType("subscription");
revenue.setEventProperties({ interval, plan_name: plan });
amplitude.revenue(revenue);
}
Experiment Integration
// lib/experiment.ts
import { Experiment, ExperimentClient } from "@amplitude/experiment-js-client";
let experimentClient: ExperimentClient | null = null;
export async function initExperiment(userId?: string): Promise<ExperimentClient> {
if (!experimentClient) {
experimentClient = Experiment.initializeWithAmplitudeAnalytics(
process.env.NEXT_PUBLIC_AMPLITUDE_DEPLOYMENT_KEY!,
{
automaticExposureTracking: true,
automaticFetchOnAmplitudeIdentityChange: true,
}
);
}
await experimentClient.fetch({ user_id: userId });
return experimentClient;
}
export function getVariant(flagKey: string): string | undefined {
if (!experimentClient) return undefined;
const variant = experimentClient.variant(flagKey);
return variant.value as string | undefined;
}
// hooks/useExperiment.ts
"use client";
import { useEffect, useState } from "react";
import { getVariant, initExperiment } from "@/lib/experiment";
export function useExperiment(flagKey: string, fallback: string = "control"): string {
const [variant, setVariant] = useState<string>(fallback);
useEffect(() => {
initExperiment().then(() => {
const value = getVariant(flagKey);
if (value) setVariant(value);
});
}, [flagKey]);
return variant;
}
// Usage in a component
function OnboardingFlow(): JSX.Element {
const variant = useExperiment("onboarding-v2", "control");
if (variant === "treatment") {
return <NewOnboarding />;
}
return <ClassicOnboarding />;
}
Server-Side Event Tracking
// Server-side tracking for webhooks and background jobs
import { getAmplitudeServer } from "@/lib/amplitude-server";
async function trackServerEvent(
userId: string,
event: string,
properties: Record<string, unknown>
): Promise<void> {
const client = getAmplitudeServer();
client.track({
user_id: userId,
event_type: event,
event_properties: properties,
time: Date.now(),
platform: "server",
});
await client.flush();
}
// Example: track subscription renewal from a webhook
async function handleRenewalWebhook(event: {
userId: string;
plan: string;
amount: number;
nextRenewal: string;
}): Promise<void> {
await trackServerEvent(event.userId, "subscription_renewed", {
plan: event.plan,
amount: event.amount,
next_renewal: event.nextRenewal,
source: "stripe_webhook",
});
}
Middleware for Page-Level Properties
// middleware.ts — Enrich analytics with request context
import { NextResponse, type NextRequest } from "next/server";
export function middleware(request: NextRequest): NextResponse {
const response = NextResponse.next();
// Pass UTM parameters to client for attribution tracking
const utm = {
source: request.nextUrl.searchParams.get("utm_source"),
medium: request.nextUrl.searchParams.get("utm_medium"),
campaign: request.nextUrl.searchParams.get("utm_campaign"),
};
if (utm.source) {
response.cookies.set("amp_utm", JSON.stringify(utm), {
maxAge: 60 * 60, // 1 hour
path: "/",
});
}
return response;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Best Practices
- Define a typed event taxonomy using TypeScript interfaces. A type-safe tracking function prevents event name typos and ensures required properties are always present.
- Use
Identify.setOnce()for immutable properties likefirst_login_dateorsignup_sourcethat should never be overwritten by subsequent identify calls. - Use
Identify.add()for counters likelogin_countorfeature_usage_countinstead of manually incrementing and setting values. - Call
amplitude.reset()on logout to clear the user ID, device ID, and all user properties, preventing cross-user data contamination. - Flush events in serverless environments by calling
client.flush()after tracking to ensure events are sent before the function terminates. - Enable
automaticExposureTrackingin the Experiment SDK so that variant assignments are tracked as analytics events automatically without manual instrumentation. - Use the Revenue class for all monetary tracking instead of custom events with amount properties. This integrates with Amplitude's built-in revenue analysis tools.
- Sample session replays to control costs. A 5-10% sample rate is usually sufficient to find usability issues while keeping data volume manageable.
Anti-Patterns
- Creating unique event names per page or feature. Events like
home_page_button_clickandpricing_page_button_clickshould be a singlebutton_clickevent with apageproperty. - Sending high-cardinality string properties. Properties like
search_queryoruser_agentas free text create millions of unique values that slow down queries. Use bucketed or categorical properties instead (e.g.,query_length_bucket: "10-20"). - Calling
amplitude.init()on the server. The browser SDK must only run client-side. Use the@amplitude/analytics-nodepackage for server-side tracking. - Tracking events before identifying the user. Events tracked before
setUserIdare associated with an anonymous device ID. While Amplitude merges them on identify, the merge is not retroactive in all analysis views. Identify as early as possible. - Using
Identify.set()for every property update. Usingset()overwrites the previous value. For properties that should reflect the first observed value, usesetOnce(). - Ignoring the experiment exposure event. If you check a variant but do not track an
exposure, the experiment results will be inaccurate. Always use the built-in automatic
exposure tracking or manually call
experimentClient.exposure(). - Not batching events. Sending each event individually with
flushQueueSize: 1creates excessive network requests. Use the default batching configuration and only reduce it in serverless contexts where function lifetime is short.
Install this skill directly: skilldb add analytics-services-skills
Related Skills
Heap
Heap: autocapture product analytics, session replay, retroactive event definition, funnel and retention analysis, JavaScript SDK
June.so
June.so: B2B product analytics, company-level insights, feature adoption reports, activation tracking, Node and JavaScript SDK
Mixpanel Analytics
"Mixpanel: event-based analytics, funnels, retention, user profiles, cohorts, group analytics, JavaScript/Node SDK"
Plausible Analytics
"Plausible: privacy-friendly analytics, no cookies, lightweight script, custom events, goals, API, self-hosted option"
PostHog Analytics
"PostHog: product analytics, event tracking, feature flags, session recordings, A/B testing, self-hosted/cloud, Next.js SDK"
Segment
Segment: customer data platform, event routing, identity resolution, analytics.js, server-side sources, warehouse destinations