Add Clerk Auth to a Next.js Template (2026)
Add Clerk Auth to a Next.js Template in 2026
Clerk is the fastest path to production auth in Next.js. The free tier covers 10,000 monthly active users, the UI components look great out of the box, organizations and roles are first-class, and the SDK is genuinely well-designed. For most SaaS products, it pays for itself in saved engineering time within a month.
This guide walks the full wiring: install, configure middleware, protect routes, handle webhooks, manage organizations, and the patterns that hold up as your product grows.
Skip the auth screen work entirely: get every kit for $499
The SaaS Starter Kit ships with auth UI (login, signup, forgot, OTP, 2FA) already laid out and themed. Drop Clerk's <SignIn/> or your own form into the existing pages and the design is done. All Access unlocks every kit on thefrontkit for $499 one-time.
Why Clerk Beats Building Auth Yourself in 2026
Auth in 2026 is harder than it was in 2020. You need:
- Email + password with secure password requirements
- OAuth providers (Google, GitHub, Apple, Microsoft at minimum)
- Magic link sign-in
- SMS-based 2FA
- TOTP-based 2FA (authenticator apps)
- WebAuthn / passkeys
- Session management with refresh tokens
- Password reset flow that resists token replay
- Account recovery for lost 2FA devices
- Suspicious activity detection
- GDPR-compliant data export and deletion
- Bot protection and rate limiting
Building this yourself is six months of work and three security audits. Clerk has all of it, audited, and updated as standards change. The trade-off is vendor dependency and pricing at scale.
The realistic alternatives:
- Auth.js (NextAuth) — free, open source, requires UI work and more configuration. Best when budget matters more than time.
- Supabase Auth — solid, free, pairs with the database. Less polished UI than Clerk.
- Auth0 — enterprise-grade, expensive, overkill for early-stage products.
- Clerk — best DX, paid above 10k MAU, fastest setup.
Pick Clerk if your time is worth more than $25/month per 10k extra users (it usually is).
Step 1: Create the Clerk Application
Sign up at clerk.com and create an application. Pick the providers you want enabled at creation (email, Google, GitHub are the standard set). You can add more later.
Grab two values from the dashboard:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY— safe for the browserCLERK_SECRET_KEY— server-side only
Step 2: Install the SDK
npm install @clerk/nextjs
Add to .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
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
Step 3: Wrap the App in ClerkProvider
In 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>
);
}
<ClerkProvider> makes the Clerk client available to every component and handles session state on the server.
Step 4: Add the Middleware
Create middleware.ts at the project root:
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/settings(.*)',
'/api/protected(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
The middleware runs on every request. If the route matches the protected list and the user isn't signed in, Clerk redirects to the sign-in page.
Step 5: Use Clerk's Prebuilt Components
The fastest path. Create app/sign-in/[[...sign-in]]/page.tsx:
import { SignIn } from '@clerk/nextjs';
export default function Page() {
return <SignIn />;
}
And app/sign-up/[[...sign-up]]/page.tsx:
import { SignUp } from '@clerk/nextjs';
export default function Page() {
return <SignUp />;
}
These render Clerk's polished, accessible auth UI. Customize colors and layout via the appearance prop or a CSS theme.
Step 6: Use Your Template's Auth UI (Custom Flow)
Most templates ship their own login form, signed up to the project's design. The pattern for wiring Clerk into a custom form:
'use client';
import { useSignIn } from '@clerk/nextjs';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
export function CustomSignIn() {
const { signIn, setActive } = useSignIn();
const router = useRouter();
const [error, setError] = useState('');
async function handleSubmit(formData: FormData) {
if (!signIn) return;
try {
const result = await signIn.create({
identifier: formData.get('email') as string,
password: formData.get('password') as string,
});
if (result.status === 'complete') {
await setActive({ session: result.createdSessionId });
router.push('/dashboard');
}
} catch (err: any) {
setError(err.errors?.[0]?.message || 'Sign in failed');
}
}
return (
<form action={handleSubmit}>
<input name="email" type="email" />
<input name="password" type="password" />
{error && <p className="text-red-500">{error}</p>}
<button>Sign in</button>
</form>
);
}
OAuth from a custom button:
'use client';
import { useSignIn } from '@clerk/nextjs';
export function GoogleSignIn() {
const { signIn } = useSignIn();
async function handleGoogle() {
if (!signIn) return;
await signIn.authenticateWithRedirect({
strategy: 'oauth_google',
redirectUrl: '/sso-callback',
redirectUrlComplete: '/dashboard',
});
}
return <button onClick={handleGoogle}>Continue with Google</button>;
}
Add app/sso-callback/page.tsx:
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs';
export default function Page() {
return <AuthenticateWithRedirectCallback />;
}
Step 7: Get the User in Server Components
The two patterns:
import { auth, currentUser } from '@clerk/nextjs/server';
// For server components and server actions:
export default async function DashboardPage() {
const { userId } = await auth();
if (!userId) return null; // Middleware should have redirected, but defensive
// Get the full user object only when you need details beyond the ID:
const user = await currentUser();
return <div>Hello, {user?.firstName}</div>;
}
Use auth() for the userId; only call currentUser() when you need the full profile.
Step 8: Webhooks for User Sync
When a user signs up, you usually want a row in your own database (for foreign keys to other tables). The pattern is webhooks.
In the Clerk dashboard, set up a webhook endpoint pointing to https://your-app.com/api/webhooks/clerk and subscribe to user.created, user.updated, and user.deleted.
Create app/api/webhooks/clerk/route.ts:
import { headers } from 'next/headers';
import { Webhook } from 'svix';
import { db } from '@/lib/db';
export async function POST(req: Request) {
const headerPayload = await headers();
const svix_id = headerPayload.get('svix-id');
const svix_timestamp = headerPayload.get('svix-timestamp');
const svix_signature = headerPayload.get('svix-signature');
if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response('Missing svix headers', { status: 400 });
}
const payload = await req.json();
const body = JSON.stringify(payload);
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!);
let evt: any;
try {
evt = wh.verify(body, {
'svix-id': svix_id,
'svix-timestamp': svix_timestamp,
'svix-signature': svix_signature,
});
} catch (err) {
return new Response('Invalid signature', { status: 400 });
}
switch (evt.type) {
case 'user.created':
await db.user.create({
data: {
clerkId: evt.data.id,
email: evt.data.email_addresses[0]?.email_address,
firstName: evt.data.first_name,
lastName: evt.data.last_name,
},
});
break;
case 'user.updated':
await db.user.update({ where: { clerkId: evt.data.id }, data: { /* ... */ } });
break;
case 'user.deleted':
await db.user.delete({ where: { clerkId: evt.data.id } });
break;
}
return new Response('OK');
}
Set CLERK_WEBHOOK_SECRET in your environment from the webhook endpoint settings.
Step 9: Organizations and Roles
For B2B SaaS, every user belongs to one or more organizations. Clerk has this built in.
Enable organizations in the Clerk dashboard. Then:
import { auth } from '@clerk/nextjs/server';
export default async function TeamPage() {
const { orgId, orgRole, orgSlug } = await auth();
if (!orgId) return <div>Select an organization</div>;
// orgRole is 'org:admin', 'org:member', or a custom role you define
return <div>You are {orgRole} in {orgSlug}</div>;
}
Use <OrganizationSwitcher /> for the UI:
import { OrganizationSwitcher } from '@clerk/nextjs';
export function Topbar() {
return <OrganizationSwitcher />;
}
For role-based access in server components:
const { has } = await auth();
const canManageTeam = has({ role: 'org:admin' });
if (!canManageTeam) {
return <div>Forbidden</div>;
}
Common Gotchas
Skipping the middleware. Without it, protected routes are accessible to anyone. Always wire the middleware and test the redirect with an incognito tab.
Calling currentUser() when you only need the ID. currentUser() fetches the full profile from Clerk's API. auth() returns the session immediately from the JWT. Use auth() 95 percent of the time.
Forgetting webhook signature verification. Without it, anyone can POST fake events to your webhook and create user records. Always verify with svix.
Not syncing users on signup. If you skip webhooks and try to create user rows lazily on first login, you'll have race conditions and missing foreign keys. Webhook on user.created is the safer pattern.
Hardcoding sign-in URLs. Use the environment variables (NEXT_PUBLIC_CLERK_SIGN_IN_URL etc.) so you can change paths without editing every redirect.
Forgetting OAuth callback page. OAuth needs /sso-callback to handle the redirect. Without it, the OAuth flow finishes nowhere.
Free tier limits. 10,000 MAU on the free tier; $25/month for the Pro tier. Know which one you're on so the billing isn't a surprise.
Adjacent Reads
- How to Build a SaaS in Next.js — broader build path
- How to Choose a Next.js SaaS Starter Kit — kit evaluation rubric
- Connect Supabase to a Next.js Template — pair Clerk with Supabase
FAQ
Should I use Clerk or Auth.js for a Next.js SaaS? Both work. Clerk is faster to ship and has better UI out of the box. Auth.js is free and open source. The math: Clerk is $25/month above 10k MAU. If a week of engineering time costs you more than $25/month for the foreseeable future, Clerk pays for itself. For most funded startups, Clerk is the right call. For bootstrapped products with strict cost constraints, Auth.js wins.
Can I use Clerk's Sign-In component AND a custom design?
Yes. The <SignIn appearance={...}> prop accepts CSS classes, colors, and layout overrides. For full control over markup, use the useSignIn() hook and build your own form (the Step 6 pattern above). Most teams use the prebuilt component initially and switch to custom only when the design needs are specific.
How do I add a custom role beyond admin/member?
In the Clerk dashboard under Organizations > Settings > Roles. Define a role like org:editor with permissions like team:invite. Then check it in code with has({ role: 'org:editor' }). Roles propagate immediately; no code changes needed when you add new ones.
Do I need webhooks if I just want to know the user ID?
No, you only need webhooks if you're storing user data in your own database. If you only need the Clerk userId (which is fine for many apps that pull all data from Clerk), skip webhooks. As soon as you have a users table in your database with foreign keys to other tables, webhooks become mandatory.
Can I migrate from Auth.js to Clerk later? Yes, but it's painful. Plan to invite all existing users to sign in with Clerk (which creates new Clerk userIds) and then map old user IDs to new ones in a migration table. Some teams keep both auth providers running for 3-6 months during the transition. Pick the right auth provider from day one if you can.
How does Clerk handle data residency for GDPR? Clerk stores user data in their US infrastructure by default. For EU customers with data residency requirements, the Enterprise tier offers EU data centers. If you have strict residency requirements, evaluate Clerk Enterprise or stick with self-hosted Auth.js + your own EU database.
