Skip to Content
ExamplesSaaS Onboarding

SaaS Onboarding Example

Production-ready onboarding flow with localStorage persistence and analytics tracking.


Complete Onboarding Component

components/OnboardingTour.tsx
'use client'; import { useState, useEffect } from 'react'; import { Tour } from 'nfsfu234-tour-guide'; export default function OnboardingTour() { const [showTour, setShowTour] = useState(false); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); // Check if user has completed onboarding const hasCompletedOnboarding = localStorage.getItem('onboarding_completed'); const tourSkippedAt = localStorage.getItem('onboarding_skipped_at'); // Show tour if: // 1. Never completed AND // 2. Never skipped OR skipped more than 7 days ago if (!hasCompletedOnboarding) { if (!tourSkippedAt) { const timer = setTimeout(() => setShowTour(true), 100); return () => clearTimeout(timer); } else { const skippedDate = new Date(tourSkippedAt); const daysSinceSkipped = (Date.now() - skippedDate.getTime()) / (1000 * 60 * 60 * 24); if (daysSinceSkipped > 7) { const timer = setTimeout(() => setShowTour(true), 100); return () => clearTimeout(timer); } } } }, []); const handleComplete = () => { // Mark as completed localStorage.setItem('onboarding_completed', 'true'); localStorage.setItem('onboarding_completed_at', new Date().toISOString()); // Track completion if (typeof window !== 'undefined' && (window as any).analytics) { (window as any).analytics.track('Onboarding Completed', { timestamp: new Date().toISOString(), }); } setShowTour(false); }; const handleSkip = () => { // Mark as skipped (can show again later) localStorage.setItem('onboarding_skipped_at', new Date().toISOString()); // Track skip if (typeof window !== 'undefined' && (window as any).analytics) { (window as any).analytics.track('Onboarding Skipped', { timestamp: new Date().toISOString(), }); } setShowTour(false); }; const handleStepChange = (stepIndex: number) => { // Track step views if (typeof window !== 'undefined' && (window as any).analytics) { (window as any).analytics.track('Onboarding Step Viewed', { step: stepIndex + 1, stepName: steps[stepIndex]?.target, }); } }; const steps = [ { target: '#dashboard', content: 'This is your dashboard. See your key metrics and recent activity at a glance.', contentMobile: 'Your dashboard overview', position: 'bottom', }, { target: '#create-project', content: 'Create your first project here. Projects help you organize your work.', contentMobile: 'Create projects here', position: 'bottom', }, { target: '#team-invite', content: 'Invite team members to collaborate. Click here to send invitations.', contentMobile: 'Invite your team', position: 'left', }, { target: '#integrations', content: 'Connect with tools you already use like Slack, GitHub, and more.', contentMobile: 'Connect integrations', position: 'right', device: 'desktop', }, { target: '#settings', content: 'Customize your workspace settings, billing, and preferences.', contentMobile: 'Your settings', position: 'bottom', }, ]; if (!mounted) return null; return ( <Tour tourId="saas-onboarding-v2" isActive={showTour} steps={steps} theme="dark" accentColor="#10b981" showProgress={true} welcomeScreen={{ enabled: true, title: 'Welcome to ProductName! 🎉', message: 'Thanks for signing up! Let me show you around so you can get started quickly. This will only take 60 seconds.', startButtonText: 'Show Me Around', }} buttonLabels={{ next: 'Next →', previous: '← Back', skip: 'Skip Tour', finish: 'Start Building! 🚀', }} onStart={() => { if (typeof window !== 'undefined' && (window as any).analytics) { (window as any).analytics.track('Onboarding Started'); } }} onStepChange={handleStepChange} onSkip={handleSkip} onComplete={handleComplete} /> ); }

Usage in Next.js App

app/dashboard/page.tsx
import OnboardingTour from '@/components/OnboardingTour'; export default function DashboardPage() { return ( <> <OnboardingTour /> <div id="dashboard"> {/* Dashboard content */} </div> <button id="create-project"> Create Project </button> <button id="team-invite"> Invite Team </button> <nav id="integrations"> {/* Integrations */} </nav> <div id="settings"> {/* Settings */} </div> </> ); }

Manual Trigger Button

Allow users to restart the tour:

components/HelpMenu.tsx
'use client'; export default function HelpMenu() { const restartTour = () => { // Clear completion flags localStorage.removeItem('onboarding_completed'); localStorage.removeItem('onboarding_skipped_at'); // Reload page to trigger tour window.location.reload(); }; return ( <div className="help-menu"> <button onClick={restartTour}> 🎓 Restart Product Tour </button> </div> ); }

Analytics Integration

Segment

// Track with Segment if (window.analytics) { window.analytics.track('Onboarding Completed', { userId: user.id, completedAt: new Date().toISOString(), stepsViewed: 5, }); }

Google Analytics

// Track with GA4 if (window.gtag) { window.gtag('event', 'onboarding_completed', { event_category: 'engagement', event_label: 'product_tour', }); }

Mixpanel

// Track with Mixpanel if (window.mixpanel) { window.mixpanel.track('Onboarding Completed', { 'Onboarding Version': 'v2', 'Steps Completed': 5, }); }

PostHog

// Track with PostHog if (window.posthog) { window.posthog.capture('onboarding_completed', { onboarding_version: 'v2', steps_completed: 5, }); }

Advanced Features

User Segmentation

Show different tours based on user type:

const getUserTourSteps = (userRole: string) => { const commonSteps = [ { target: '#dashboard', content: 'Your dashboard' }, ]; const roleSpecificSteps = { admin: [ { target: '#admin-panel', content: 'Admin controls' }, { target: '#user-management', content: 'Manage users' }, ], user: [ { target: '#my-tasks', content: 'Your assigned tasks' }, ], }; return [...commonSteps, ...(roleSpecificSteps[userRole] || [])]; }; <Tour steps={getUserTourSteps(user.role)} />

Progressive Onboarding

Show different tours at different stages:

const getOnboardingStage = () => { if (!localStorage.getItem('stage_1_completed')) return 'stage_1'; if (!localStorage.getItem('stage_2_completed')) return 'stage_2'; return 'completed'; }; const stage1Steps = [ { target: '#basics', content: 'Let\'s start with the basics' }, ]; const stage2Steps = [ { target: '#advanced', content: 'Now let\'s explore advanced features' }, ]; const stage = getOnboardingStage(); if (stage === 'stage_1') { return <Tour steps={stage1Steps} onComplete={() => { localStorage.setItem('stage_1_completed', 'true'); }} />; }

Feature Announcements

Show tours for new features:

const showFeatureTour = () => { const lastVersion = localStorage.getItem('last_seen_version'); const currentVersion = '2.0.0'; if (lastVersion !== currentVersion) { return true; } return false; }; if (showFeatureTour()) { <Tour steps={newFeatureSteps} welcomeScreen={{ enabled: true, title: 'New Features! 🎉', message: 'Check out what\'s new in version 2.0', }} onComplete={() => { localStorage.setItem('last_seen_version', '2.0.0'); }} /> }

Metrics to Track

Completion Metrics

  • Completion Rate: Users who finish / Users who start
  • Skip Rate: Users who skip / Total users shown tour
  • Average Steps Viewed: Average steps before skip/complete
  • Time to Complete: How long users take

Behavioral Metrics

  • Feature Adoption: Usage of features shown in tour
  • Retention: Return rate of users who completed vs skipped
  • Support Tickets: Reduction after tour implementation

Example Dashboard Query (SQL)

SELECT COUNT(CASE WHEN event = 'Onboarding Completed' THEN 1 END) as completed, COUNT(CASE WHEN event = 'Onboarding Skipped' THEN 1 END) as skipped, COUNT(CASE WHEN event = 'Onboarding Started' THEN 1 END) as started, ROUND(100.0 * COUNT(CASE WHEN event = 'Onboarding Completed' THEN 1 END) / NULLIF(COUNT(CASE WHEN event = 'Onboarding Started' THEN 1 END), 0), 2) as completion_rate FROM analytics_events WHERE created_at >= NOW() - INTERVAL '30 days';

Best Practices

1. Keep It Short

  • 3-5 steps max for initial onboarding
  • Focus on “must know” features
  • Save advanced features for later

2. Personalize

  • Show relevant steps based on user role
  • Skip steps for features they can’t access
  • Adapt messaging to use case

3. Allow Re-triggering

  • Add “Take Tour Again” in help menu
  • Show tour again after major updates
  • Let users restart if they skip

4. Track Everything

  • Monitor completion rates
  • Identify drop-off points
  • A/B test different tour flows

5. Test on Real Users

  • Beta test before full rollout
  • Collect feedback
  • Iterate based on data

Next Steps

Last updated on