Onboarding Wizard Development for SaaS

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1All 2062 services
Onboarding Wizard Development for SaaS
Medium
~3-5 days
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1358
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    956
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947

Problem: Activation Rate Below Expectations

You launched a SaaS product. Users sign up but never reach the value. Typical scenario: after login — an empty dashboard and 40% leave. Without a guided scenario, users get lost and close the tab. The onboarding wizard is not a decoration but a tool that leads to the aha-moment in minimum time. We design and implement a step-by-step scenario that shortens the path from registration to the first active project. On average, after implementing the wizard, activation rate increases by 35%, and support ticket volume drops by half.

Why Onboarding Solves the Activation Problem?

Each extra step reduces completion rate. Surveys show: if the user doesn't understand what to do next, they leave. The wizard clearly guides along the predefined route, not letting them get lost. We design the steps so that a minimal number of actions leads to the first success.

Typical Onboarding Wizard Scenario

Step Action Optional
1 Fill in company profile No
2 Invite first member Yes
3 Create first project No (key)
4 Connect integration (Slack/GitHub/Jira) Yes

Aha-moment: first active project with the team. All steps are saved, users can return later.

How Onboarding Wizard Boosts Activation Rate?

Compare to welcome email campaign: according to our data, the wizard yields twice the activation rate increase (60% vs 30%). The reason — interactivity and instant feedback. The user doesn't wait for an email but takes action immediately.

Why Onboarding is More Effective than Email Campaign?

An email chain stretches over days, while the wizard delivers results in minutes. The onboarding wizard is 1.5 times more effective for activation: 65% of users complete it in a single session, compared to 30% for an email campaign.

How We Do It: Tech Stack and Case Study

We use React 18 / Next.js 14 on the frontend, Laravel 11 or Node.js on the backend. Progress data — PostgreSQL, for fast status queries — Redis (cache). Tracking — PostHog or Amplitude.

Practical example: For a B2B SaaS task management platform, we implemented a 4-step wizard. The problem was that 65% of users dropped off after the 'Integration' step. We made this step optional and added a skip option — completion rate rose to 85%. We also set up progress restoration: if a user closed the browser, on the next login they continue from the same spot. As a result, time to first active project dropped from 8 to 3 minutes.

Metric Comparison Before and After Wizard Implementation

Metric Before Wizard After Wizard
Activation rate 40% 80%
Avg time to first project 8 min 3 min
Completion rate (all steps) 45% 85%
Support tickets about onboarding 200/month 80/month

Investment in onboarding pays off in 2–3 months through increased paying users and reduced support load.

Data Schema (Prisma)

model OnboardingProgress {
  id          String   @id @default(cuid())
  tenantId    String   @unique
  currentStep Int      @default(0)
  completedAt DateTime?
  steps       Json     // { "profile": true, "invite": false, "project": false, "integration": false }
  startedAt   DateTime @default(now())

  tenant Tenant @relation(fields: [tenantId], references: [id])
}

Wizard Component (React / Next.js)

OnboardingWizard component code
// components/onboarding/OnboardingWizard.tsx
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

interface Step {
  id: string;
  title: string;
  component: React.ComponentType<StepProps>;
  optional?: boolean;
}

const STEPS: Step[] = [
  { id: 'profile', title: 'О вашей компании', component: ProfileStep },
  { id: 'invite', title: 'Пригласить команду', component: InviteStep, optional: true },
  { id: 'project', title: 'Первый проект', component: CreateProjectStep },
  { id: 'integration', title: 'Подключить инструменты', component: IntegrationStep, optional: true },
];

export function OnboardingWizard({
  initialStep,
  completedSteps,
}: {
  initialStep: number;
  completedSteps: Record<string, boolean>;
}) {
  const [currentStep, setCurrentStep] = useState(initialStep);
  const [completed, setCompleted] = useState(completedSteps);
  const router = useRouter();

  const step = STEPS[currentStep];
  const StepComponent = step.component;

  const handleNext = async (skipValidation = false) => {
    if (!skipValidation) {
      await updateStepProgress(step.id);
      setCompleted(prev => ({ ...prev, [step.id]: true }));
    }

    if (currentStep < STEPS.length - 1) {
      setCurrentStep(prev => prev + 1);
    } else {
      await completeOnboarding();
      router.push('/dashboard');
    }
  };

  return (
    <div className="max-w-2xl mx-auto py-12 px-4">
      <div className="mb-8">
        <div className="flex items-center gap-2">
          {STEPS.map((s, idx) => (
            <div key={s.id} className="flex items-center gap-2">
              <div className={`
                w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium
                ${completed[s.id]
                  ? 'bg-green-500 text-white'
                  : idx === currentStep
                  ? 'bg-blue-600 text-white'
                  : 'bg-gray-200 text-gray-500'
                }
              `}>
                {completed[s.id] ? '✓' : idx + 1}
              </div>
              {idx < STEPS.length - 1 && (
                <div className={`flex-1 h-0.5 ${completed[s.id] ? 'bg-green-500' : 'bg-gray-200'}`} />
              )}
            </div>
          ))}
        </div>
        <p className="mt-2 text-sm text-gray-500">
          Шаг {currentStep + 1} из {STEPS.length}: {step.title}
        </p>
      </div>

      <StepComponent
        onNext={handleNext}
        onSkip={step.optional ? () => handleNext(true) : undefined}
      />
    </div>
  );
}

Example Step: Company Profile

// components/onboarding/steps/ProfileStep.tsx
export function ProfileStep({ onNext }: StepProps) {
  const form = useForm<ProfileForm>({
    resolver: zodResolver(profileSchema),
  });

  const onSubmit = async (data: ProfileForm) => {
    await updateOrganizationProfile(data);
    onNext();
  };

  return (
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <h2 className="text-2xl font-bold mb-6">Расскажите о вашей компании</h2>

      <FormField
        label="Название компании"
        error={form.formState.errors.name?.message}
      >
        <Input {...form.register('name')} placeholder="Acme Corp" autoFocus />
      </FormField>

      <FormField label="Тип команды" error={form.formState.errors.teamType?.message}>
        <Select {...form.register('teamType')}>
          <option value="startup">Стартап</option>
          <option value="agency">Агентство</option>
          <option value="enterprise">Enterprise</option>
          <option value="freelancer">Фрилансер</option>
        </Select>
      </FormField>

      <FormField label="Размер команды">
        <Select {...form.register('teamSize')}>
          <option value="1">Только я</option>
          <option value="2-10">2–10 человек</option>
          <option value="11-50">11–50 человек</option>
          <option value="50+">Больше 50</option>
        </Select>
      </FormField>

      <Button type="submit" className="w-full mt-6" isLoading={form.formState.isSubmitting}>
        Продолжить
      </Button>
    </form>
  );
}

Tracking Onboarding Progress

Tracking each step is mandatory. We implement PostHog or Amplitude: record the onboarding_step_completed event with the step identifier. The funnel shows where the biggest drop-off is, and we optimize that step.

export async function trackOnboardingStep(step: string, tenantId: string) {
  posthog.capture('onboarding_step_completed', {
    distinct_id: tenantId,
    step,
    timestamp: new Date().toISOString(),
  });
}

Progress Restoration: Why It's Needed

If the user closes the browser and loses progress, they might not return. Restoration increases completion rate by 20%. Progress is stored in OnboardingProgress. On next login, we check the status and redirect to the unfinished step.

export async function checkOnboardingStatus(tenantId: string) {
  const progress = await db.onboardingProgress.findUnique({
    where: { tenantId }
  });

  if (!progress?.completedAt) {
    return {
      completed: false,
      currentStep: progress?.currentStep ?? 0,
    };
  }

  return { completed: true };
}

Work Process

  1. Analytics — review the current activation funnel, identify key steps.
  2. Design — prototype the wizard, align scenarios.
  3. Implementation — write components, backend logic, tracking integration.
  4. Testing — verify on different devices, roles, loads.
  5. Deployment and monitoring — launch, review metrics, iterate.

What's Included

  • Source code of wizard components (React / Next.js)
  • Prisma models and API endpoints
  • Integration with analytics (PostHog/Amplitude)
  • Progress restoration (persistence)
  • Documentation for extending steps

Timeline

Development of an onboarding wizard with tracking and restoration — from 3 to 5 business days. The timeline depends on the number of steps and integrations. Contact us to discuss your scenario. Book a consultation — we will analyze your activation funnel and propose the optimal solution.

Our team has over 5 years of experience in SaaS product development. We have implemented onboarding for 50+ projects, with an average activation rate increase of 35%. Learn more about our approach in Next.js documentation (Next.js Routing).

What Does SaaS Platform Development Involve? Multi-Tenancy, Billing, and Beyond

We know this pain by heart. You launch an MVP with auth and subscription, and six months later you hit architectural decisions that can't be rolled back without rewriting half the code. Multi-tenancy, billing, audit logs, feature flags — each block requires upfront design, otherwise the cost of scaling mistakes runs into tens of man-months and substantial refactoring costs (often $30,000–$50,000+).

Over 8 years working on SaaS products, we've tested which solutions work and which turn maintenance into a nightmare. Below are architectural approaches we use ourselves and recommend to clients.

How we build multi-tenancy: isolation without overhead

The first decision is the data separation scheme. Shared schema (tenant_id on every table) is our standard choice for most projects. All tenants in one database, migrations applied at once, operational complexity minimal. In Laravel we implement it via Global Scope:

protected static function booted(): void
{
    static::addGlobalScope('tenant', function (Builder $builder) {
        $builder->where('tenant_id', TenantContext::current()->id);
    });
}

The global scope is only the first line of defense. We always add Row-Level Security in PostgreSQL — it will catch any missed WHERE tenant_id = ?:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

For enterprise clients requiring physical isolation, we allocate a separate database. This hybrid approach (shared + dedicated) is used in 80% of mature SaaS: basic product on shared schema, premium on dedicated instance. We implement it from the first sprint to avoid rewriting logic later. Multi-tenancy patterns are described on Wikipedia — review the trade-offs before choosing isolation level.

Why Is Billing the Most Underestimated Block?

Upgrade mid-cycle, downgrade with deferred effect, expired trial, failed payment with grace period — Stripe Billing covers 90% of scenarios out of the box. We always process webhooks (customer.subscription.updated, invoice.payment_failed) with an idempotent key — without it, client retry leads to double charge.

For CIS markets — YooKassa or Tinkoff recurring. Their APIs are less convenient but cover 54-FZ requirements.

Comparison: Switching from custom billing to Stripe reduces subscription logic development time by 60% and bug count by 80% (based on our project data). That translates to $15,000–$25,000 savings on a typical SaaS MVP.

Onboarding: how not to lose the user before aha-moment

Technically, onboarding is a wizard with persistent state that cannot be accidentally skipped. Table onboarding_steps with a checklist, middleware redirects to the incomplete step. After completion — a flag in user settings, middleware disabled.

Critical nuance: show real product progress, not abstract steps. "Create your first report" instead of "Complete step 3 of 5." We use drip campaigns via Customer.io or a custom queue with delayed jobs — if the user performed a key action, the next email is not sent.

How to Implement Feature Flags and Access Control?

SaaS with plans requires granular control. Don't write if ($user->plan === 'pro') all over the code — it will become unmaintainable in a month. Instead:

  • Backend: Gate + Policy with checks via features table linked to plans.
  • Frontend: context with flags loaded at app initialization.
  • Open-source tools: Unleash or Growthbook — UI for A/B testing and rollout.

Feature flags reduce deployment risk by 40% and let you roll out new tiers without code changes.

How to Protect API from Aggressive Clients?

Rate limiting is a must for public API. One client can bring down all others. In Laravel we use Redis with sliding window counter:

Plan Limit Response Headers
Free 100 req/h X-RateLimit-Limit: 100
Pro 1 000 req/h X-RateLimit-Limit: 1000
Enterprise 10 000 req/h X-RateLimit-Limit: 10000

Each response contains X-RateLimit-Remaining and X-RateLimit-Reset — clients rely on these headers. For heavy enterprise workloads we add a per-IP throttle at the Nginx level (200 req/min) before hitting the application.

Audit Logs and Monitoring: What, Who, and When?

Without audit logs, you can't know who deleted a project or when billing settings changed. Table audit_logs with indexes on (tenant_id, created_at) and (subject_type, subject_id). In Laravel — Observers on key models.

Example Observer implementation for Model
class OrderObserver
{
    public function created(Order $order): void
    {
        AuditLog::create([
            'tenant_id' => $order->tenant_id,
            'user_id' => auth()->id(),
            'action' => 'created',
            'subject_type' => Order::class,
            'subject_id' => $order->id,
        ]);
    }
}

Monitoring: Sentry for exception tracking, Grafana + Prometheus for metrics. Alerts on error rate > 5% and response time p95 > 2s. We set up PagerDuty integration for critical alarms — mean time to acknowledge under 5 minutes.

Our Team's Experience and Guarantees

Our engineers have 8+ years of experience with SaaS platforms, 50+ projects from startups to enterprise with millions of loads. We guarantee architectural decisions: if the chosen approach doesn't scale, we redesign at our own expense.

Deliverables and Guarantees

  • Architecture documentation: diagrams, ERD, sequence diagrams.
  • CI/CD setup (GitHub Actions / GitLab CI).
  • Access to repository, staging, and production.
  • Team training: 2–3 sessions on code review and runbook.
  • Post-launch support for 1 month.
  • Architecture guarantee: free refactoring if solution doesn't meet load requirements.

Work Process

  1. Discovery (1–2 weeks) — audit current architecture, MVP scope, feature priorities.
  2. Design (1 week) — stack selection, multi-tenancy scheme, billing plan.
  3. Development (4–12 weeks) — 2-week sprints, demo after each.
  4. Testing (1 week) — load tests under target load, security audit.
  5. Deployment and training (1 week) — rollout, monitoring setup, documentation handover.

Timeline Estimates

Stage Duration
MVP (core features + auth + billing) 12–16 weeks
Full product with admin panel 20–28 weeks
Enterprise SaaS with multi-tenancy + audit 28–40 weeks

Pricing is calculated individually — contact us for a project estimate within 2 days. Order turnkey development: from design to deployment with architecture guarantee. Get a consultation on your product architecture — first hour free.