Skip to main content
Back to Blog
Tutorials
3 min read
November 26, 2024

How to Add Authentication to Next.js with Clerk

Set up user authentication in Next.js with Clerk. Covers sign-up, sign-in, protected routes, middleware, and user management in under 30 minutes.

Ryel Banfield

Founder & Lead Developer

Authentication is complex. Clerk handles the hard parts — sign up, sign in, session management, multi-factor auth, social logins — while you focus on your application logic. Here is how to integrate it with Next.js App Router.

Step 1: Create a Clerk Application

  1. Go to clerk.com and create an account
  2. Create a new application
  3. Choose your sign-in methods (email, Google, GitHub, etc.)
  4. Copy your API keys

Step 2: Install Clerk

pnpm add @clerk/nextjs

Step 3: Add Environment Variables

# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up

Step 4: Add ClerkProvider

Wrap your application with ClerkProvider:

// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}

Step 5: Add Middleware

Protect routes and handle authentication redirects:

// middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isPublicRoute = createRouteMatcher([
  "/",
  "/about",
  "/blog(.*)",
  "/contact",
  "/pricing",
  "/sign-in(.*)",
  "/sign-up(.*)",
  "/api/webhooks(.*)",
]);

export default clerkMiddleware(async (auth, request) => {
  if (!isPublicRoute(request)) {
    await auth.protect();
  }
});

export const config = {
  matcher: [
    // Skip Next.js internals and static files
    "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    // Always run for API routes
    "/(api|trpc)(.*)",
  ],
};

This makes all routes require authentication by default, except the ones listed in isPublicRoute.

Step 6: Create Sign-In and Sign-Up Pages

// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";

export default function SignInPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignIn />
    </div>
  );
}
// app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from "@clerk/nextjs";

export default function SignUpPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignUp />
    </div>
  );
}

Clerk provides pre-built, customizable UI components for these flows.

Step 7: Show User Status in the Navbar

// components/layout/UserButton.tsx
import { SignedIn, SignedOut, UserButton, SignInButton } from "@clerk/nextjs";

export function AuthButton() {
  return (
    <>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
      </SignedIn>
      <SignedOut>
        <SignInButton mode="modal">
          <button className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white">
            Sign In
          </button>
        </SignInButton>
      </SignedOut>
    </>
  );
}

Step 8: Access User Data

In Server Components

// app/dashboard/page.tsx
import { currentUser } from "@clerk/nextjs/server";

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

  if (!user) {
    return null; // Middleware handles redirect
  }

  return (
    <main>
      <h1>Welcome, {user.firstName}</h1>
      <p>Email: {user.emailAddresses[0]?.emailAddress}</p>
    </main>
  );
}

In Client Components

"use client";

import { useUser } from "@clerk/nextjs";

export function ProfileCard() {
  const { user, isLoaded } = useUser();

  if (!isLoaded) return <div>Loading...</div>;
  if (!user) return null;

  return (
    <div>
      <img src={user.imageUrl} alt={user.fullName || ""} />
      <h2>{user.fullName}</h2>
      <p>{user.primaryEmailAddress?.emailAddress}</p>
    </div>
  );
}

In API Routes

// app/api/user/route.ts
import { auth } from "@clerk/nextjs/server";

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

  if (!userId) {
    return new Response("Unauthorized", { status: 401 });
  }

  // userId is available for database queries
  const data = await db.query.users.findFirst({
    where: eq(users.clerkId, userId),
  });

  return Response.json(data);
}

Step 9: Custom Sign-In Appearance

Customize Clerk's UI to match your brand:

<ClerkProvider
  appearance={{
    variables: {
      colorPrimary: "#3b82f6",
      borderRadius: "0.5rem",
    },
    elements: {
      formButtonPrimary: "bg-blue-600 hover:bg-blue-700",
      card: "shadow-lg",
    },
  }}
>

Step 10: Webhooks for Database Sync

Sync Clerk user data with your database:

// app/api/webhooks/clerk/route.ts
import { Webhook } from "svix";
import { headers } from "next/headers";

export async function POST(request: Request) {
  const headerPayload = await headers();
  const svixId = headerPayload.get("svix-id");
  const svixTimestamp = headerPayload.get("svix-timestamp");
  const svixSignature = headerPayload.get("svix-signature");

  const body = await request.text();

  const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
  const event = wh.verify(body, {
    "svix-id": svixId!,
    "svix-timestamp": svixTimestamp!,
    "svix-signature": svixSignature!,
  }) as { type: string; data: Record<string, unknown> };

  switch (event.type) {
    case "user.created":
      // Create user in your database
      break;
    case "user.updated":
      // Update user in your database
      break;
    case "user.deleted":
      // Delete user from your database
      break;
  }

  return new Response("OK", { status: 200 });
}

Security Notes

  • Never expose CLERK_SECRET_KEY to the client
  • Always verify webhook signatures
  • Use middleware to protect routes — do not rely on client-side checks
  • Store only the Clerk user ID in your database, not passwords

Need Authentication?

We implement authentication, authorization, and user management in Next.js applications. Contact us for secure authentication integration.

authenticationClerkNext.jsauthtutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles