Guides / SaaS
Most SaaS churn happens before users ever experience value. If you don't track onboarding steps, you can't see where users abandon the flow.
First, map out your onboarding steps and give them numeric IDs:
// lib/onboarding.ts
export const ONBOARDING_STEPS = {
ACCOUNT_CREATED: 1,
PROFILE_COMPLETED: 2,
FIRST_PROJECT_CREATED: 3,
SDK_INSTALLED: 4,
FIRST_EVENT_LOGGED: 5,
TEAM_INVITED: 6,
} as const;
type OnboardingStep = keyof typeof ONBOARDING_STEPS;
import { logit } from "@/lib/logit";
export async function trackOnboardingStep(
userId: string,
step: OnboardingStep,
metadata?: Record<string, string | number>
) {
const stepNumber = ONBOARDING_STEPS[step];
await logit.now("onboarding", {
event: `Onboarding step ${stepNumber}: ${step.replace(/_/g, " ").toLowerCase()}`,
description: `User ${userId} completed step ${stepNumber}/${Object.keys(ONBOARDING_STEPS).length}`,
icon: stepNumber === Object.keys(ONBOARDING_STEPS).length ? "🎉" : "✅",
notify: step === "FIRST_EVENT_LOGGED",
tags: { step: String(stepNumber), stepName: step },
metadata: { userId, step, stepNumber, ...metadata },
});
}
// Run this via a cron job: every day, check users who signed up 3 days ago but haven't completed step 3+
async function checkAbandonedOnboarding() {
const cutoff = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
const stuckUsers = await db.user.findMany({
where: {
createdAt: { lt: cutoff },
onboardingStep: { lt: 3 },
},
take: 100,
});
for (const user of stuckUsers) {
await logit.now("onboarding-abandoned", {
event: "User stuck in onboarding",
description: `Stuck at step ${user.onboardingStep} for 3+ days`,
icon: "⚠️",
notify: false,
metadata: { userId: user.id, step: user.onboardingStep, email: user.email },
});
}
}
With this setup, your LogIt dashboard shows:
onboarding channel: step-by-step progress per useronboarding-abandoned channel: who is stuck and wherestepName tag to see exact drop-off countsTry LogIt free
7-day trial. No credit card required.