Vitalis

How to Create Custom Clerk Auth Forms and Google authentication with NextJS, and Tailwind

How to Create Custom Clerk Auth Forms and Google authentication with NextJS, andTailwind


Recently, while working on a project, I needed to set up a custom authentication system that matched our brand’s style. I decided to create a custom Clerk Auth form with our own design. To get started, I looked for resources and guides, but most didn’t quite meet my needs.

My specific requirements were:

  1. A sign-in option using a code for verification.

  2. Google authentication for a smooth, one-click sign-in.

After diving into Clerk’s documentation, I found that using JavaScript components was the best approach to achieve this (Clerk also offers “Clerk Elements,” but it’s currently in beta).

In this post, I’ll walk you through how to set up user authentication with a verification code during sign-up and how to add Google sign-up options, using an example to guide each step.

N/B — for google ensure you have a Google developer account

N/B — for google ensure you have a Google developer accounthere is a guide for that

Steps we will cover

Steps we will cover

  1. Setting Up Clerk and Next.js Hooks

  2. Initializing State Variables

  3. Loading State Management

  4. Handling Sign-Up with Email

  5. Handling Google Sign-Up

  6. Handling Verification Code Submission

  7. Handling Resend Verification Code

  8. Loading and Conditional Rendering

This is more of functionality, for forms you can create for yourself.
here is my relative path
app/sign-up/[[…sign-up]]/page.jsx

Are you Ready?

Excited Word GIF by Desus & Mero

  1. Setting Up Clerk and Next.js Hooks

We will import hooks from Clerk and Next.js, allowing us to handle sign-up and authentication within the app. These hooks simplify managing authentication states and redirecting users upon successful sign-up.

bash
import { useState, useEffect } from "react";
import { useSignUp, useAuth } from "@clerk/nextjs";
import { useRouter } from "next/navigation";

Remember to add use-client

2. Initializing State Variables

2. Initializing State Variables
We will set up state variables to manage error messages, verification state, email tracking and whether content should display. By managing these states, the app provides clear feedback during each step of the sign-up process, ensuring users know exactly what’s happening.

bash
const [error, setError] = useState("");
const [verifying, setVerifying] = useState(false);
const [email, setEmail] = useState("");
const [isCompleting, setIsCompleting] = useState(false);
const [shouldShowContent, setShouldShowContent] = useState(false);

3. Loading State Management

3. Loading State Management

The next step is to check if the app is loaded and whether the user is signed in. We will display content if the app is fully loaded avoiding partially loaded components or errors. Always verify the app’s loading state before allowing user interactions to improve user experience.

bash
useEffect(() =>{
  if (!isLoaded) return;
  if (isSignedIn) { router.replace('/'); } else { setShouldShowContent(true); }
}, [isLoaded, isSignedIn, router]);

Take a moment and implement that, then be ready for the real stuff.

Take a moment and implement that, then be ready for the real stuff.

https://cdn-images-1.medium.com/v2/resize:fit:800/0*WoIZIBsBkgsDPH6a.gif

4. Handling Sign-Up with Email

4. Handling Sign-Up with Email

In this step we will create a new user with the provided email and start the verification process.
The email verification is key to preventing fake accounts and ensuring account security.

bash
const handleSignUp = async (formData) =>{
  if (!isLoaded) return;
  try {
    await signUp.create({
      firstName: formData.firstName, lastName: formData.lastName, emailAddress: formData.emailAddress, strategy: "email_code"
    });
    setEmail(formData.emailAddress);
    setVerifying(true);
  } catch (err) {
    if (err.code === 'form_identifier_exists') {
      setError("An account with this Google email already exists. Please sign in instead.");
    } else { setError(err.errors[0].message); }
  }
};

5. Handling Google Sign-Up

5. Handling Google Sign-Up

Let’s handle individual signing up with Google, this reduces sign-up friction by allowing users to sign up without filling out forms.

bash
const handleGoogleSignUp = async () =>{
  if (!isLoaded) return;
  try {
    await signUp.authenticateWithRedirect({
      strategy: "oauth_google", redirectUrl: "/sign-in", redirectUrlComplete: "/",
    });
  } catch (err) {
    if (err.code === 'authentication_failed') {
      setError("Authentication with Google failed. Please try again.");
    } else if (err.code === 'form_identifier_exists') {
      setError("An account with this Google email already exists. Please sign in instead.");
    } else {
      setError(err.errors[0]?.message || "An error occurred during Google sign-up. Please try again.");
    }
  }
};

6. Handling Verification Code Submission

6. Handling Verification Code Submission

Let’s go back and handle people signing up with email, let’s ensure they verify their accounts

bash
const handleVerify = async (code) =>{
  if (!isLoaded) return;
  setIsCompleting(true);
  try {
    const completeSignUp = await signUp.attemptEmailAddressVerification({ code });
    if (completeSignUp.status === "complete") {
      await setActive({ session: completeSignUp.createdSessionId });
      router.replace('/');
    } else {
      setIsCompleting(false);
      setError("Verification failed. Please try again.");
    }
  } catch (err) { setError(err.errors[0].message); setIsCompleting(false); }
};

7. Handling Resend Verification Code

7. Handling Resend Verification Code

Let’s handle if the verification expires or they don’t receive it, we can handle this by re-sending the code.

bash
const handleResendCode = async () =>{
  if (!isLoaded) return;
  try {
    await signUp.prepareEmailAddressVerification({ strategy: "email_code" });
  } catch (err) { setError("Failed to resend code. Please try again."); }
};

8. Loading and Conditional Rendering

8. Loading and Conditional Rendering

Let’s handle different parts of the screen loading.

bash
if (!shouldShowContent || isCompleting || !isLoaded || isSignedIn) {
  return (<div className="fixed h-screen top-0 left-0 right-0 bottom-0 flex flex-col gap-6 justify-center items-center bg-card dark:bg-card z-[100]"><Your loader /></div>);
}

FinallyHere is the return with the different components

Finally
Here is the return with the different components

bash
return (<div className="">{
  !verifying ? (<yoursignupform onSubmit={handleSignUp} onGoogleSignUp={handleGoogleSignUp} error={error} />) : (<yourverifyform onSubmit={handleVerify} onResend={handleResendCode} error={error} email={email} />)
}</div>)

That’s a wrap.

Thats All Folks Clap GIF by Digital Pratik


You can share how useful this was on the comments.