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
- Go to clerk.com and create an account
- Create a new application
- Choose your sign-in methods (email, Google, GitHub, etc.)
- 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_KEYto 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.