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
This feature is implemented in the @kit/onboarding
package.
The onboarding process is built on the top of the Stepper component.
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.
// 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>; }
We are using an completedOnboarding
column in the user
table to check if the user has completed the onboarding process.
This simple procedure allows us to make sure that all users are onboarded before accessing the dashboard.
Onboarding page
Installation
Usage
We gonna put the onboarding page in a your-app/app/onboarding/
folder.
// 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
:
Prop | Type | Default |
---|---|---|
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
:
// 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/
:
'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
:
// 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:
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:
<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
Premade templates for emails to make your brand more professional.
Guide users through organization setup, team creation, and member invitations after registration.
How is this guide?
Last updated on 10/17/2025