GET FULL ACCESS
Back to all articles Traversy Media Journal

Next.js Authentication With Clerk

next.js react Jul 10, 2023

Authentication and user management can be a real pain in the neck. Especially when you are dealing with front-end frameworks like React. There are many different ways to implement authentication and a lot of different services that you can use. In this article, we are going to look at setting up not only authentication, but complete user management with a tool called Clerk. Clerk can be used with a React SPA, Next.js, Remix and more and can be easily integrated with databases like Firebase and Supabase. You can also easily integrate it with your own custom backend user authorization.

Updated for the current Clerk SDK: This guide uses the Next.js App Router, clerkMiddleware(), asynchronous server helpers, and Clerk's current redirect settings.

In this article, we will be using Next.js and we will setup complete user management from scratch using Clerk.

Use these official Clerk guides alongside this article:

Next.js Setup

Let's start by setting up a brand new Next.js project. We can do this by running the following command:

npx create-next-app@latest

I am going to choose all of the defaults. I am not using TypeScript. I am not using an src folder and I am using the app directory/layout. I am also going to choose to use Tailwind CSS for styling. You can choose whatever you like.

Let's just clear out the page.js file. It should look like this:

export default function Home() {
  return (
    <>
      <h1>Home</h1>
    </>
  );
}

I also prefer to use the jsx extension, so I am going to change page.js to page.jsx. I am also going to change the layout.js to layout.jsx.

Styles

If you selected Tailwind CSS when creating the app, create-next-app configures it for you. Keep the generated app/globals.css file. In a current Tailwind setup, it includes:

@import 'tailwindcss';

Installation

Now we can install Clerk. We can do this by running the following command:

npm install @clerk/nextjs

If you were using something like Create React App, you would install @clerk/react instead.

Clerk Setup & Environment Keys

You need to sign up at Clerk and create a new project. Once you have created a project, you will be given a Clerk Frontend API Key. You will need to add this to your .env.local file. They will look something like this:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_YOU
CLERK_SECRET_KEY=sk_test_YOUR_SECRET_KEY

Login Methods

You can choose how you want users to login by going to User & Authentication -> Email, Phone, Username. I will use Email and Password with an email verification. You can also choose to have users login using only an email verification link.

I will also choose to collect the user's name.

You can choose the social login methods that you want to be used by going to User & Authentication -> Social Connections. I am going to choose Google and GitHub. You can choose whatever you like.

Clerk Provider

Wrap the application content with <ClerkProvider> inside the <body> element. Open app/layout.jsx and use the following:

import './globals.css';
import { Inter } from 'next/font/google';
import { ClerkProvider } from '@clerk/nextjs';
import Header from './components/header';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: 'Clerk App',
  description: 'Example Clerk App',
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <ClerkProvider afterSignOutUrl="/">
          <Header />
          <main className="container mx-auto">{children}</main>
        </ClerkProvider>
      </body>
    </html>
  );
}

The provider makes Clerk's components and authentication state available throughout the application. The sign-out redirect is configured here because the old afterSignOutUrl prop on <UserButton> is deprecated.

Let's create a very simple header component. Create a folder called components in the app folder. Inside of that folder, create a file called header.jsx. Add the following code to the file:

import Link from 'next/link';

const Header = ({ username }) => {
  return (
    <nav className="bg-blue-700 py-4 px-6 flex items-center justify-between mb-5">
      <div className="flex items-center">
        <Link href="/">
          <div className="text-lg uppercase font-bold text-white">
            Clerk App
          </div>
        </Link>
      </div>
      <div className="text-white">
        <Link href="sign-in" className="text-gray-300 hover:text-white mr-4">
          Sign In
        </Link>
        <Link href="sign-up" className="text-gray-300 hover:text-white mr-4">
          Sign Up
        </Link>
      </div>
    </nav>
  );
};

export default Header;

Right now, it shows the auth links, but once we are authenticated, we will show the username and a logout link. We will do that in a bit.

Protecting Pages

First, add Clerk's request middleware. With current Next.js versions, create proxy.js in the project root. If you use Next.js 15 or earlier, name the file middleware.js instead.

import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware();

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
    '/__clerk/(.*)',
  ],
};

clerkMiddleware() provides authentication state, but it does not protect routes by default. Clerk now recommends protecting each server-side resource close to where it is used.

For example, protect the dashboard page in app/dashboard/page.jsx:

import { auth } from '@clerk/nextjs/server';

export default async function DashboardPage() {
  const { isAuthenticated, redirectToSignIn } = await auth();

  if (!isAuthenticated) {
    return redirectToSignIn();
  }

  return <h1>Dashboard</h1>;
}

Use the same server-side check in Route Handlers and Server Actions that read or change protected data.

Clerk Branding

Clerk displays a "Secured by Clerk" badge on its prebuilt components by default. You can test removing it in development mode, but removing the badge in production requires a paid plan. The setting is under Settings and then Branding in the Clerk Dashboard.

Custom Sign-In and Sign-Up Routes

You can host Clerk's prebuilt sign-in and sign-up components on your own routes. Add these settings to .env.local:

NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/dashboard
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/dashboard

The fallback URLs are used when Clerk does not receive a more specific redirect URL. The older NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL and NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL variables are no longer the current API.

Catch All Routes

We are going to use the Next.js catch all route to handle all of our pages. Create a folder in the app directory called sign-up. Inside of that create another folder called [[...sign-up]]. Then create a file in that folder called page.jsx. Now we have a catch-all route for all sign-up pages.

Add the following code to the file:

import { SignUp } from '@clerk/nextjs';

const SignUpPage = () => {
  return (
    <>
      <SignUp />
    </>
  );
};
export default SignUpPage;

Let's do the same for sign in. Create a folder called sign-in, another folder in that one called [[...sign-in]] and add a file named page.jsx.

Add the following code to the file:

import { SignIn } from '@clerk/nextjs';

const SignInPage = () => {
  return (
    <>
      <SignIn />
    </>
  );
};
export default SignInPage;

Dashboard

The dashboard page created in the Protecting Pages section calls await auth() before rendering. Signed-out visitors are redirected to the sign-in page, while signed-in users can view the dashboard.

Log In

Let's try creating an account. I am going to choose to create an account using Google. Once I authenticate, I am redirected to the dashboard. If you go to the Clerk dashboard, you will see that the user has been created.

UserButton

The <UserButton /> component is a component that you can use to show the user's name and a logout button. Let's add this to our header. Open the Header.jsx file and add the following code:

import Link from 'next/link';
import { UserButton } from '@clerk/nextjs';

const Header = ({ username }) => {
  return (
    <nav className="bg-blue-700 py-4 px-6 flex items-center justify-between mb-5">
      <div className="flex items-center">
        <Link href="/">
          <div className="text-lg uppercase font-bold text-white">
            Clerk App
          </div>
        </Link>
      </div>
      <div className="text-white flex items-center">
        <Link href="sign-in" className="text-gray-300 hover:text-white mr-4">
          Sign In
        </Link>
        <Link href="sign-up" className="text-gray-300 hover:text-white mr-4">
          Sign Up
        </Link>
        <div className="ml-auto">
          <UserButton />
        </div>
      </div>
    </nav>
  );
};

export default Header;

Now you can see an avatar with a dropdown containing a sign-out action and account settings. The sign-out redirect is configured on <ClerkProvider>.

Account Settings

You can access your account settings from here as well. This includes email addresses, connected accounts, the ability to delete your account and more.

I think for the tiny amount of code that we have written, this is amazing.

Email Login

Before we do anything else, let's logout and then register with an email and password.

Click on 'Sign Up' And fill out the form. You will be redirected to a form to input a code.

You should have received an email with a code.

Enter the code and you will be logged in and redirected to the dashboard.

Conditional Rendering

In a Server Component, import auth() from @clerk/nextjs/server and await it. This gives you the current user ID without making a separate user API request.

import Link from 'next/link';
import { UserButton } from '@clerk/nextjs';
import { auth } from '@clerk/nextjs/server';

export default async function Header() {
  const { userId } = await auth();

  return (
    <nav className="flex items-center justify-between px-6 py-4 bg-blue-700">
      <Link href="/" className="text-lg font-bold text-white">
        Clerk App
      </Link>

      <div className="flex items-center gap-4 text-white">
        {!userId && (
          <>
            <Link href="/sign-in">Sign In</Link>
            <Link href="/sign-up">Sign Up</Link>
          </>
        )}
        {userId && <UserButton />}
      </div>
    </nav>
  );
}

currentUser()

When you need the complete user object in a Server Component, use currentUser(). It performs a Clerk Backend API request, so use auth() when the user ID or session data is enough.

import { currentUser } from '@clerk/nextjs/server';

export default async function Welcome() {
  const user = await currentUser();

  if (!user) {
    return <p>Not signed in</p>;
  }

  return <p>Welcome, {user.firstName}</p>;
}

useAuth() Hook

For a Client Component, add the 'use client' directive and use the useAuth() hook:

'use client';

import { useAuth } from '@clerk/nextjs';

export default function SessionDetails() {
  const { isLoaded, userId, sessionId } = useAuth();

  if (!isLoaded) {
    return <p>Loading...</p>;
  }

  if (!userId) {
    return <p>Not signed in</p>;
  }

  return (
    <p>
      User {userId}, session {sessionId}
    </p>
  );
}

<UserProfile /> Component

You can embed Clerk's account settings interface on a dedicated profile route:

import { UserProfile } from '@clerk/nextjs';

export default function ProfilePage() {
  return <UserProfile />;
}

Link to this page only for signed-in users and protect any server-side data the page uses.

Themes

You can customize the appearance of Clerk components by using themes. They offer a set of themes that can be used with the appearance prop. You can install the themes package with:

npm i @clerk/themes

Now, go into your layout.jsx file and import the theme:

import { dark } from '@clerk/themes';

Then add it to the <ClerkProvider /> component:

<ClerkProvider
  appearance={{
    baseTheme: dark,
  }}
>

Now you can see that the appearance has changed.

I'll change it back to the light theme.

import { light } from '@clerk/themes';

<ClerkProvider
  appearance={{
    baseTheme: light,
  }}
>

Custom Authentication UI

If Clerk's prebuilt components do not fit your design, you can build a custom flow with useSignUp() and useSignIn(). The current API uses the SignUpFuture and SignInFuture resources.

For an email and password sign-up, the current flow is:

  1. Call signUp.password({ emailAddress, password }).
  2. Send a verification code with signUp.verifications.sendEmailCode().
  3. Verify the code with signUp.verifications.verifyEmailCode({ code }).
  4. When the status is complete, call signUp.finalize().

Custom authentication flows must also handle validation errors, bot protection, multifactor authentication, and session tasks. Use Clerk's current email and password flow guide for the complete implementation instead of copying an older flow that may omit required states.

Conclusion

There you have it. We have complete authentication and user management in our app. You can also customize the look of things from within the Clerk dashboard. If you want to change to using a magic link instead of a code, you can do that as well. In my opinion, this is the easiest way to add authentication to your app and you get a ton of features.

You can find the original project repository here. It accompanies the 2023 version of the tutorial, so follow the updated code in this article and Clerk's current documentation when APIs differ.

Keep learning: Build complete applications with Next.js From Scratch.

Related reading: Next.js Expense Tracker With Prisma, Neon & Clerk.

Stay connected with news and updates!

Join our mailing list to receive the latest news and updates from our team.
Don't worry, your information will not be shared.

We hate SPAM. We will never sell your information, for any reason.