Next.js Authentication with Clerk — Complete Guide
Next.js Authentication with Clerk (2026): The Complete Integration Guide
clerkMiddlewareLive demo: clerk-demo.devkitmarket.com GitHub repo: github.com/devkit-market/nextjs-clerk-2026
5-Minute Overview — The Workflow
- Environment Setup: Create a Clerk application and configure .text
.env.local - Provider & Middleware: Wrap your app and protect routes with .text
clerkMiddleware - Sign-In UI: Drop in pre-built components for sign-in, sign-up, and user button.
- Server-Side Auth: Read the current user inside Server Components and Server Actions.
- Webhook Sync: Mirror Clerk users into your own database with Svix-verified webhooks.
Step 1 — API Keys & Dependencies
npm install @clerk/nextjs svix
.env.local# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...
# Optional — customize Clerk's redirect URLs
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
Step 2 — Provider & Route Protection
<ClerkProvider>clerkMiddlewareapp/layout.tsximport { ClerkProvider } from "@clerk/nextjs";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
middleware.tssrc/import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isProtectedRoute = createRouteMatcher([
"/dashboard(.*)",
"/settings(.*)",
"/api/private(.*)",
]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect(); // Redirects to sign-in if not authenticated
}
});
export const config = {
matcher: [
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|svg|ico)).*)",
"/(api|trpc)(.*)",
],
};
Step 3 — Sign-In, Sign-Up & User Button
app/sign-in/[[...sign-in]]/page.tsximport { 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<SignUp />[[...sign-in]]import {
SignedIn,
SignedOut,
SignInButton,
UserButton,
} from "@clerk/nextjs";
export default function Header() {
return (
<header className="flex items-center justify-between p-4 border-b">
<h1 className="font-bold">My App</h1>
<div>
<SignedOut>
<SignInButton mode="modal">
<button className="px-4 py-2 bg-indigo-600 text-white rounded-lg">
Sign In
</button>
</SignInButton>
</SignedOut>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</div>
</header>
);
}
Step 4 — Reading User Data on the Server (Critical)
// app/dashboard/page.tsx
import { auth, currentUser } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const { userId } = await auth();
if (!userId) redirect("/sign-in");
const user = await currentUser();
return (
<div className="p-8">
<h1>Welcome back, {user?.firstName}</h1>
<p>Your email: {user?.emailAddresses[0].emailAddress}</p>
</div>
);
}
// app/actions/createPost.ts
"use server";
import { auth } from "@clerk/nextjs/server";
export async function createPost(content: string) {
const { userId } = await auth();
if (!userId) throw new Error("Unauthorized");
// Now safe to write to your DB with userId
await db.post.create({ data: { content, authorId: userId } });
}
auth()Step 5 — Webhook Implementation
app/api/webhook/clerk/route.tsimport { headers } from "next/headers";
import { NextResponse } from "next/server";
import { Webhook } from "svix";
import type { WebhookEvent } from "@clerk/nextjs/server";
export async function POST(req: Request) {
const body = await req.text(); // raw body — do NOT use req.json()
const headerPayload = headers();
const svixHeaders = {
"svix-id": headerPayload.get("svix-id")!,
"svix-timestamp": headerPayload.get("svix-timestamp")!,
"svix-signature": headerPayload.get("svix-signature")!,
};
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
let event: WebhookEvent;
try {
event = wh.verify(body, svixHeaders) as WebhookEvent;
} catch (err) {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
}
// Handle the event
switch (event.type) {
case "user.created":
const { id, email_addresses, first_name, last_name } = event.data;
await db.user.create({
data: {
clerkId: id,
email: email_addresses[0].email_address,
firstName: first_name,
lastName: last_name,
},
});
break;
case "user.updated":
await syncUserUpdate(event.data);
break;
case "user.deleted":
await db.user.delete({ where: { clerkId: event.data.id! } });
break;
default:
console.log(\`Unhandled event type \${event.type}\`);
}
return NextResponse.json({ received: true });
}
CLERK_WEBHOOK_SECRETuser.createduser.updateduser.deletedngrok http 3000Step 6 — Production Checklist
- Switch to Production Instance: Create a separate production app in the Clerk dashboard and use /text
pk_live_keys in your hosting environment.textsk_live_ - Custom Domain: Configure for Clerk's hosted pages — looks more trustworthy than the defaulttext
accounts.yourdomain.comURL.textclerk.accounts.dev - Update CVE-Affected Next.js Versions: Upgrade past Next.js 15.2.3 to patch CVE-2025-29927, which allows middleware bypass via crafted headers.
- Webhook Idempotency: Clerk may retry webhooks. Use on your DB writes so duplicates don't create double records.text
upsert - Always Re-Check on the Server: Never trust client-side for sensitive data. Always re-verify withtext
<SignedIn>in Server Components and Actions.textauth() - Branding: Customize colors, logo, and theme in Clerk Dashboard → Customization for a seamless brand experience.
Conclusion
clerkMiddlewareSkip the setup and start shipping
Love this guide? All these patterns are pre-configured in our **SaaS Starter Pro** kit. Save 40+ hours of development.
Explore the KitRelated Articles
Selected insights to level up your development workflow.
How to Add Stripe to Next.js (2026)
A complete walkthrough of integrating Stripe Checkout and webhooks into your Next.js application.
How to Add Razorpay to Next.js (2026): Complete Guide with Code
Step-by-step guide to integrate Razorpay payment gateway in Next.js 15 with App Router, TypeScript, webhooks, and refunds.
Next.js + Prisma + Stripe Tutorial
Learn how to build a subscription-based SaaS using the powerhouse trio of Next.js, Prisma, and Stripe.
Keep building with free resources
Production-ready starter kits and zero-friction developer tools — the same ones we use to ship our own products.
Starter Kits
Next.js Blog Kit
MDX-powered blog with full SEO, dark mode, RSS feed, reading time, and syntax highlighting. Deploy to Vercel in one click.
Developer Tools
Shadcn/UI Component Previewer
Live preview of shadcn/ui components with instant copy-paste code. Browse rendered components and grab snippets.
Next.js Project Structure Generator
8.5kSelect your stack and instantly get a production-ready folder structure. Copy the entire scaffold in one click.
.env File Generator
24kPick your tech stack and get a complete, commented .env boilerplate file. Never forget an environment variable.
Tailwind CSS Color Palette Generator
15k+Enter a brand color and generate a complete Tailwind-compatible shade scale with config snippets.
Looking for something specific?
Browse the full library — 7+ kits across 4+ categories.