Features
PreviousNext

Onboarding

Add an onboarding process after registration to collect more data on your new users and to perform specific actions.

In this page, we will see :

  • how to setup your database for authentication

In the kit, only the apps/dashboard application implement the onboarding feature.

Redirection

After registration, the user is redirected to the /dashboard endpoint. You are using the /dashboard/layout.tsx file to redirect the user to the onboarding page if the user is not onboarded.

your-app/app/dashboard/layout.tsx
// imports ... export default async function OrganizationsLayout( props: React.PropsWithChildren ): Promise<React.JSX.Element> { const db = await getDBClient(); const user = await db.user.get(); if (!user) { return redirect(dashboardRoutes.paths.auth.signIn); } if (!user.completedOnboarding) { // Redirect to the onboarding page return redirect(dashboardRoutes.paths.onboarding.index); } return <UserProvider user={user}>{props.children}</UserProvider>; }

This simple procedure allows us to make sure that all users are onboarded before accessing the dashboard.

Onboarding page

Installation

pnpm add '@kit/onboarding@workspace:*'

Usage

We gonna put the onboarding page in a your-app/app/onboarding/ folder.

your-app/app/onboarding/page.tsx
// imports ... import { UserOnboarding } from '@kit/onboarding'; import { redirect } from 'next/navigation'; export default async function OnboardingFullPage(): Promise<React.JSX.Element> { const db = await getDBClient(); const user = await db.user.require(); if (user.completedOnboarding) { // Redirect to the dashboard, if the user has already completed the onboarding process return redirect(dashboardRoutes.paths.dashboard.index); } return ( <div className="relative min-h-screen"> {/* Logout button, logo ... */} <UserOnboarding activeSteps={['profile', 'theme']} metadata={{ user: user }} redirectUrl={dashboardRoutes.paths.dashboard.index} /> </div> ); }

We are using the UserOnboarding component to display the onboarding process.

Details of the props UserOnboardingProps :

PropTypeDefault
metadata*
OnboardingUserMetadata
activeSteps
UserOnboardingStep[]
redirectUrl*
string

Add new steps

Adding new onboarding steps involves several components working together. Here's how to extend the onboarding flow:

1. Define the Step Schema

First, create a Zod schema for your new step in kit/www/features/onboarding/src/onboarding.schema.ts:

kit/www/features/onboarding/src/onboarding.schema.ts
// Add your new step schema export const yourNewStepSchema = z.object({ field1: z.string().min(1, 'Field 1 is required'), field2: z.number().min(0).max(100), }); // Update the UserOnboardingStep type to include your new step export type UserOnboardingStep = 'profile' | 'theme' | 'yourNewStep'; // Update the main schema to include your step data export const completeUserOnboardingSchema = z.object({ activeSteps: z.array(z.enum(['profile', 'theme', 'yourNewStep'])), profileStep: profileOnboardingSchema.optional(), themeStep: themeOnboardingSchema.optional(), yourNewStep: yourNewStepSchema.optional(), // Add your step here });

2. Create the Step Component

Create a new component for your step in kit/www/features/onboarding/src/components/:

kit/www/features/onboarding/src/components/onboarding-your-new-step.tsx
'use client'; import { cn } from '@kit/shared'; import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@kit/ui/form'; import { Input } from '@kit/ui/input'; import { StepperNext } from '@kit/ui/stepper'; import React from 'react'; import { useFormContext } from 'react-hook-form'; import { type CompleteUserOnboardingSchema } from '../onboarding.schema'; import { OnboardingUserStepProps } from './user-onboarding'; export type OnboardingYourNewStepProps = Pick<React.HtmlHTMLAttributes<HTMLDivElement>, 'className'> & OnboardingUserStepProps; export function OnboardingYourNewStep({ className, loading, }: OnboardingYourNewStepProps): React.JSX.Element { const methods = useFormContext<CompleteUserOnboardingSchema>(); return ( <div className={cn('flex flex-col gap-4', className)}> <h1 className="text-xl leading-none font-semibold tracking-tight lg:text-2xl"> Your New Step Title </h1> <p className="text-muted-foreground text-sm lg:text-base"> Description of what this step does. </p> <div className="grid gap-x-8 gap-y-4"> <FormField control={methods.control} name="yourNewStep.field1" render={({ field }) => ( <FormItem> <FormLabel required>Field 1</FormLabel> <FormControl> <Input type="text" required disabled={loading} {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={methods.control} name="yourNewStep.field2" render={({ field }) => ( <FormItem> <FormLabel required>Field 2</FormLabel> <FormControl> <Input type="number" min="0" max="100" required disabled={loading} {...field} onChange={(e) => field.onChange(parseInt(e.target.value, 10))} /> </FormControl> <FormMessage /> </FormItem> )} /> </div> <div className="mt-12 flex justify-end"> <StepperNext loading={loading} /> </div> </div> ); }

3. Register the Step

Update the USER_ONBOARDING_STEP configuration in kit/www/features/onboarding/src/components/user-onboarding.tsx:

kit/www/features/onboarding/src/components/user-onboarding.tsx
// Import your new component import { OnboardingYourNewStep } from './onboarding-your-new-step'; export const USER_ONBOARDING_STEP: Record<UserOnboardingStep, OnboardingStepConfig> = { profile: { name: 'Profile', description: 'Set up your profile information', component: OnboardingProfileStep, }, theme: { name: 'Theme', description: 'Choose your preferred theme', component: OnboardingThemeStep, }, // Add your step to the configuration yourNewStep: { name: 'Your New Step', description: 'Description of your new step', component: OnboardingYourNewStep, }, };

4. Update the Server Action

Modify the server action in kit/www/features/onboarding/src/onboarding.action.ts to handle your new step data:

kit/www/features/onboarding/src/onboarding.action.ts
export const completeUserOnboarding = authActionClient .metadata({ actionName: 'completeUserOnboarding', }) .schema( z.object({ values: completeUserOnboardingSchema, redirectUrl: z.string(), }) ) .action(async ({ ctx: { db }, parsedInput: { values, redirectUrl } }) => { try { const currentUser = await db.user.require(); await db.rls.transaction(async (tx) => { if (values.activeSteps.includes('profile') && values.profileStep) { // Update profile data... } // Handle your new step data if (values.activeSteps.includes('yourNewStep') && values.yourNewStep) { // Process your new step data here... } }); // Handle theme step if needed... revalidatePath(redirectUrl); return { redirectUrl, }; } catch (error) { console.error('Error completing onboarding:', error); return { serverError: 'Failed to complete onboarding', }; } });

5. Update the Usage

Finally, update your onboarding page to include the new step:

your-app/app/onboarding/page.tsx
<UserOnboarding
    activeSteps={['profile', 'theme', 'yourNewStep']}
    metadata={{ user: user }}
    redirectUrl={dashboardRoutes.paths.dashboard.index}
/>

Key Points

  • Modular Design: Each step is self-contained with its own component, schema, and processing logic
  • Type Safety: Use TypeScript interfaces and Zod schemas to ensure data integrity
  • Form Integration: Steps automatically integrate with the form system using useFormContext
  • Server Processing: Handle step-specific logic in the server action within the transaction
  • Flexibility: You can add, remove, or reorder steps by modifying the activeSteps array

How is this guide?

Last updated on 10/17/2025