User Onboarding

Implementing a user onboarding page and collecting information on sign-up

By default, Stack Auth only collects email addresses and user names during sign-up, provided these details are available from the OAuth providers or sign-in methods used. If you want to ensure these details are collected, gather additional information such as phone numbers or addresses, or display an onboarding tutorial for new users, you need to create a custom user onboarding page.

The most straightforward approach is to redirect users to an onboarding page right after they sign up. However, this not recommended for the following reasons:

  1. Users can easily accidently (or purposely) close or navigate away from the page before completing the onboarding.
  2. Redirect URLs may vary depending on the context. For instance, if a user is redirected to a sign-in page after trying to access a protected page, they’ll expect to return to the original protected page post-authentication.

Instead, a more reliable strategy is to store an onboarded flag in the user’s metadata and redirect users to the onboarding page if they haven’t completed it yet.

Example implementation

First, let’s create an onboarding page to collect the user’s name and address, then update the user’s metadata (more details on metadata here).

app/onboarding/page.tsx
1'use client';
2import { useEffect, useState } from 'react';
3import { useUser } from '@stackframe/stack';
4import { useRouter } from 'next/navigation';
5
6export default function OnboardingPage() {
7 const user = useUser();
8 const router = useRouter();
9 const [displayName, setDisplayName] = useState(user.displayName);
10 const [address, setAddress] = useState('');
11
12 // Redirect to home if the user has already onboarded
13 useEffect(() => {
14 if (user.clientMetadata.onboarded) {
15 router.push('/');
16 }
17 }, [user]);
18
19 const handleSubmit = async (e) => {
20 e.preventDefault();
21 await user.update({
22 displayName,
23 clientMetadata: {
24 onboarded: true,
25 address,
26 },
27 });
28 };
29
30 return (
31 <form onSubmit={handleSubmit}>
32 <input
33 type="text"
34 value={displayName}
35 onChange={(e) => setDisplayName(e.target.value)}
36 />
37 <input
38 type="text"
39 value={address}
40 onChange={(e) => setAddress(e.target.value)}
41 />
42 <button type="submit">Submit</button>
43 </form>
44 );
45}

While the above implementation offers a basic onboarding process, users can still skip onboarding by directly sending an API request to the Stack Server to update the clientMetadata.onboarded flag. If you want to ensure that onboarding cannot be bypassed on the API level, you should create a server endpoint to validate and store the data, then save the onboarded flag in the clientReadonlyMetadata on the server side after validation.

Next, we can create a hook/function to check if the user has completed onboarding and redirect them to the onboarding page if they haven’t.

app/onboarding-hooks.ts
1'use client';
2import { useEffect } from 'react';
3import { useUser } from '@stackframe/stack';
4import { useRouter } from 'next/navigation';
5
6export function useOnboarding() {
7 const user = useUser();
8 const router = useRouter();
9
10 useEffect(() => {
11 if (!user.clientMetadata.onboarded) {
12 router.push('/onboarding');
13 }
14 }, [user]);
15}

You can then use these functions in locations where onboarding is required (typically in areas where you interact with the user object).

app/page.tsx
1import { useOnboarding } from '@/app/onboarding-hooks';
2import { useUser } from '@stackframe/stack';
3
4export default function HomePage() {
5 useOnboarding();
6 const user = useUser();
7
8 return (
9 <div>Welcome to the app, {user.displayName}</div>
10 );
11}