User Onboarding

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

By default, Stack Auth collects information such as email addresses from OAuth providers. Sometimes, you may want to collect additional information from users during sign-up, for example a name or address.

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

  1. Users can accidentally (or purposefully) 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

Let’s say you have an onboarding page that asks for an address and stores it in the user’s metadata:

app/onboarding/page.tsx
1export default function OnboardingPage() {
2 const user = useUser();
3 const router = useRouter();
4 const [address, setAddress] = useState('');
5
6
7 return <>
8 <input
9 type="text"
10 value={address}
11 onChange={(e) => setAddress(e.target.value)}
12 />
13
14 <button onClick={async () => {
15 await user.update({
16 clientMetadata: {
17 onboarded: true,
18 address,
19 },
20 });
21 router.push('/');
22 }}>
23 Submit
24 </button>
25 </>
26 );
27}

While the above implementation offers a basic onboarding process, users can still skip onboarding by directly sending an API request 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:

app/onboarding-hooks.ts
1'use client';
2import { useEffect } from 'react';
3import { useUser } from '@stackframe/stack';
4import { useRouter } from 'next/navigation';
5
6export function useOnboarded() {
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 wherever onboarding is required:

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}