Dec 12, 2023
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.
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:
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
1export { };23declare global {4interface CustomJwtSessionClaims {5metadata: {6onboardingComplete?: boolean;7};8}9}
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:
1import { authMiddleware } from "@clerk/nextjs";2import { redirectToSignIn } from "@clerk/nextjs/server";3import { NextRequest, NextResponse } from "next/server";45// This example protects all routes including api/trpc routes6// 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 Middleware8export default authMiddleware({9publicRoutes: ["/"],10afterAuth: async (auth, req: NextRequest, evt) => {11const { userId, sessionClaims } = auth1213// For user visiting /onboarding, don't try and redirect14if (userId && req.nextUrl.pathname === "/onboarding") {15return NextResponse.next();16}1718// User isn't signed in and the route is private -- redirect to sign-in19if (!userId && !auth.isPublicRoute) return redirectToSignIn({ returnBackUrl: req.url })2021// Catch users who doesn't have `onboardingComplete: true` in PublicMetata22// Redirect them to the /onboading out to complete onboarding23if (userId && !sessionClaims?.metadata?.onboardingComplete) {24const onboardingUrl = new URL("/onboarding", req.url);25return NextResponse.redirect(onboardingUrl)26}2728// User is logged in and the route is protected - let them view.29if (userId && !auth.isPublicRoute) return NextResponse.next()3031// If the route is public, anyone can view it.32if (auth.isPublicRoute) return NextResponse.next()3334}35});3637export const config = {38matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],39};4041
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:
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'23import { auth, clerkClient } from "@clerk/nextjs/server"45export const completeOnboarding = async () => {6const { userId } = auth()78if (!userId) {9return { message: "No logged in user" }10}1112try {13await clerkClient.users.updateUser(userId, { publicMetadata: { onboardingComplete: true } })14return { message: 'User updated' }15} catch (e) {16console.log('error', e)17return { 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'23import * as React from 'react'4import { useUser } from '@clerk/nextjs'5import { useRouter } from 'next/navigation'6import { completeOnboarding } from './_actions'789export default function Onboarding() {10const { user } = useUser()11const router = useRouter()1213const handleSubmit = async () => {14await completeOnboarding()15await user?.reload()16router.push("/dashboard")17}1819return (20<>21<form action={handleSubmit}>22<button type="submit">Complete</button>23</form>24</>25)26}
Your onboarding flow is now complete. Configure the page without you need and once the user is done onboarding invoke the server action.
Start completely free for up to 10,000 monthly active users and up to 100 monthly active orgs. No credit card required.
Learn more about our transparent per-user costs to estimate how much your company could save by implementing Clerk.
The latest news and updates from Clerk, sent to your inbox.