Clerk
Blog

Back

Engineering


Dec 12, 2023

Preview

Back

Engineering


Dec 12, 2023

Onboarding for your Clerk App in 3 Steps

Roy Anger

Roy Anger


The user onboarding flow plays a crucial role in your application development journey, and Clerk simplifies this process for you.


Tutorial users Next 14.0.4 and @clerk/nextjs 4.27.6



The user onboarding flow plays a crucial role in your application development journey, and Clerk simplifies this process for you. If you need to gather extra information and/or a user leaves mid-way before completing it, then you want to ensure that their progress is automatically saved and can be resumed from where they left off on their next sign-in.

Image showing the user's path if they have completed onboarding, or if they have not.

To do this, you can leverage Clerk’s customizable session tokens, publicMetadata and Next’s Middleware to create a robust experience within a few lines of code.

In this guide, you will learn how to:

  • Add Custom Claims to your Session Token
  • Configure your middleware to read Session data
  • Set, and consume, a User’s publicMetadata to drive application state

Adding `publicMetadata` to the session

The onboarding flow will use publicMetadata to track a user’s onboarding status. We want to start by adding this to the session so it is available right from the user’s session without a network call.

Open your Clerk Dashboard, go to Sessions, and then click the ‘Edit’ button. In the modal that opens there will be a window that you can edit. Add the following and save:

1
{
2
"metadata": "{{user.public_metadata}}"
3
}

If you’re using TypeScript, add the following to types/globals.d.ts

1
export { };
2
3
declare global {
4
interface CustomJwtSessionClaims {
5
metadata: {
6
onboardingComplete?: boolean;
7
};
8
}
9
}

Build the Middleware

The middleware.ts is the workhorse of this solution. We need it to route the user to exactly where they need to go. If the user does not have onboardingComplete: true then redirect the user to /onboarding.

In addition to that, it also handles three default cases:

  1. Redirecting a user who is not signed in but trying to visit a protected page to sign-in
  2. Allowing a signed-in user to visit a protected page, and
  3. Allowing anyone to visit the public pages.

1
import { authMiddleware } from "@clerk/nextjs";
2
import { redirectToSignIn } from "@clerk/nextjs/server";
3
import { NextRequest, NextResponse } from "next/server";
4
5
// This example protects all routes including api/trpc routes
6
// Please edit this to allow other routes to be public as needed.
7
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware
8
export default authMiddleware({
9
publicRoutes: ["/"],
10
afterAuth: async (auth, req: NextRequest, evt) => {
11
const { userId, sessionClaims } = auth
12
13
// For user visiting /onboarding, don't try and redirect
14
if (userId && req.nextUrl.pathname === "/onboarding") {
15
return NextResponse.next();
16
}
17
18
// User isn't signed in and the route is private -- redirect to sign-in
19
if (!userId && !auth.isPublicRoute) return redirectToSignIn({ returnBackUrl: req.url })
20
21
// Catch users who doesn't have `onboardingComplete: true` in PublicMetata
22
// Redirect them to the /onboading out to complete onboarding
23
if (userId && !sessionClaims?.metadata?.onboardingComplete) {
24
const onboardingUrl = new URL("/onboarding", req.url);
25
return NextResponse.redirect(onboardingUrl)
26
}
27
28
// User is logged in and the route is protected - let them view.
29
if (userId && !auth.isPublicRoute) return NextResponse.next()
30
31
// If the route is public, anyone can view it.
32
if (auth.isPublicRoute) return NextResponse.next()
33
34
}
35
});
36
37
export const config = {
38
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
39
};
40
41

Onboarding

New users who haven’t completed onboarding are now landing on your /onboarding page. Build that page to handle whatever you need. The floor is open to you to build. The only thing left to do is complete the onboarding process.

This requires two things:

  1. Updating the user’s publicMetadata,
  2. Reloading the user’s session for the change to take place.

To update the publicMetadata, we can use a server action with a form in the page. Let’s start with a a server action — we’ll add this in src/app/onboarding/_actions.ts. This will use the clerkClient wrapper that Clerk provides to interact with the Backend API and update the user’s metadata.

1
'use server'
2
3
import { auth, clerkClient } from "@clerk/nextjs/server"
4
5
export const completeOnboarding = async () => {
6
const { userId } = auth()
7
8
if (!userId) {
9
return { message: "No logged in user" }
10
}
11
12
try {
13
await clerkClient.users.updateUser(userId, { publicMetadata: { onboardingComplete: true } })
14
return { message: 'User updated' }
15
} catch (e) {
16
console.log('error', e)
17
return { message: `Error updating user` }
18
}
19
}

With that in place, we’ll add a basic page in src/app/onboarding/page.tsx and setup a form with just a submit button to complete onboarding. You will likely programmatically call this action once the user has completed their tasks, so this is just a very loose example. In the handleSubmit() function we will first call the server action. Once that has finished we will then reload the user to be sure the changes are reflected in the session, and then redirect them to to the /dashboard route.

1
'use client'
2
3
import * as React from 'react'
4
import { useUser } from '@clerk/nextjs'
5
import { useRouter } from 'next/navigation'
6
import { completeOnboarding } from './_actions'
7
8
9
export default function Onboarding() {
10
const { user } = useUser()
11
const router = useRouter()
12
13
const handleSubmit = async () => {
14
await completeOnboarding()
15
await user?.reload()
16
router.push("/dashboard")
17
}
18
19
return (
20
<>
21
<form action={handleSubmit}>
22
<button type="submit">Complete</button>
23
</form>
24
</>
25
)
26
}

Wrap Up

Your onboarding flow is now complete. Configure the page without you need and once the user is done onboarding invoke the server action.

Preview
Clerk's logo

Start now,
no strings attached

Start completely free for up to 10,000 monthly active users and up to 100 monthly active orgs. No credit card required.

Start Building

Pricing built for
businesses of all sizes.

Learn more about our transparent per-user costs to estimate how much your company could save by implementing Clerk.

View pricing
Clerk's logo

Newsletter!

The latest news and updates from Clerk, sent to your inbox.

Clerk logo

Clerk - Complete User Management

TwitterLinkedInGitHubDiscordFacebook

© 2023 Clerk Inc.


product
Components

© 2023 Clerk Inc.