Clerk Cheat Sheet
Overview
Clerk is a developer-first authentication and user management platform that provides drop-in UI components for sign-up, sign-in, user profiles, and organization management. It handles the entire authentication lifecycle including email/password, social login, multi-factor authentication, magic links, passkeys, and SMS verification out of the box.
Clerk offers first-class support for React, Next.js, Remix, Expo, and JavaScript, with backend SDKs for Node.js, Python, Go, and Ruby. Its pre-built components are fully customizable and handle edge cases like session management, device tracking, and account linking, while its backend APIs provide full control over user data and authentication flows.
Installation
# Next.js
npm install @clerk/nextjs
# React
npm install @clerk/clerk-react
# Remix
npm install @clerk/remix
# Express.js
npm install @clerk/express
# Node.js backend
npm install @clerk/backend
# Expo (React Native)
npm install @clerk/clerk-expo
Next.js Setup
// 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>
);
}
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isProtectedRoute = createRouteMatcher([
"/dashboard(.*)",
"/api/private(.*)",
]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
Pre-Built Components
import {
SignIn,
SignUp,
UserButton,
UserProfile,
OrganizationSwitcher,
SignedIn,
SignedOut,
} from "@clerk/nextjs";
// Sign-in page
export default function SignInPage() {
return <SignIn />;
}
// Sign-up page
export default function SignUpPage() {
return <SignUp />;
}
// Navigation with auth state
function Header() {
return (
<nav>
<SignedIn>
<UserButton afterSignOutUrl="/" />
<OrganizationSwitcher />
</SignedIn>
<SignedOut>
<a href="/sign-in">Sign In</a>
</SignedOut>
</nav>
);
}
// Full user profile page
export default function ProfilePage() {
return <UserProfile />;
}
Hooks and Client-Side Auth
import { useAuth, useUser, useSignIn, useOrganization } from "@clerk/nextjs";
function Dashboard() {
const { isLoaded, userId, sessionId, getToken } = useAuth();
const { user } = useUser();
if (!isLoaded) return <div>Loading...</div>;
if (!userId) return <div>Not signed in</div>;
const callApi = async () => {
const token = await getToken();
const res = await fetch("/api/data", {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
};
return (
<div>
<h1>Welcome, {user?.firstName}</h1>
<p>Email: {user?.primaryEmailAddress?.emailAddress}</p>
<button onClick={callApi}>Fetch Data</button>
</div>
);
}
Server-Side Auth (Next.js App Router)
// app/api/route.ts
import { auth, currentUser } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
export async function GET() {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const user = await currentUser();
return NextResponse.json({
userId,
email: user?.emailAddresses[0]?.emailAddress,
});
}
// Server component
import { auth, currentUser } from "@clerk/nextjs/server";
export default async function ServerPage() {
const { userId } = await auth();
const user = await currentUser();
return (
<div>
<p>User ID: {userId}</p>
<p>Name: {user?.firstName} {user?.lastName}</p>
</div>
);
}
Backend API (Node.js)
import { createClerkClient } from "@clerk/backend";
const clerk = createClerkClient({
secretKey: process.env.CLERK_SECRET_KEY,
});
// List users
const users = await clerk.users.getUserList({
limit: 10,
orderBy: "-created_at",
});
// Get single user
const user = await clerk.users.getUser("user_abc123");
// Update user metadata
await clerk.users.updateUserMetadata("user_abc123", {
publicMetadata: { role: "admin" },
privateMetadata: { stripeCustomerId: "cus_xxx" },
});
// Create user
await clerk.users.createUser({
emailAddress: ["user@example.com"],
password: "SecureP@ss123!",
firstName: "John",
lastName: "Doe",
});
// Delete user
await clerk.users.deleteUser("user_abc123");
Organizations (Multi-Tenancy)
import { useOrganization, useOrganizationList } from "@clerk/nextjs";
function OrgDashboard() {
const { organization, membership } = useOrganization();
const { createOrganization, setActive, userMemberships } = useOrganizationList({
userMemberships: { infinite: true },
});
const handleCreate = async () => {
const org = await createOrganization({ name: "Acme Corp" });
await setActive({ organization: org.id });
};
return (
<div>
<p>Current org: {organization?.name}</p>
<p>Role: {membership?.role}</p>
<button onClick={handleCreate}>Create Org</button>
</div>
);
}
Webhooks
// app/api/webhooks/clerk/route.ts
import { Webhook } from "svix";
import { headers } from "next/headers";
export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
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");
const body = await req.text();
const wh = new Webhook(WEBHOOK_SECRET);
const evt = wh.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
});
const { type, data } = evt;
switch (type) {
case "user.created":
await handleUserCreated(data);
break;
case "user.updated":
await handleUserUpdated(data);
break;
case "session.created":
await handleSessionCreated(data);
break;
}
return new Response("OK", { status: 200 });
}
Advanced Usage
Custom Sign-In Flow
import { useSignIn } from "@clerk/nextjs";
function CustomSignIn() {
const { signIn, setActive } = useSignIn();
const handleSubmit = async (email: string, password: string) => {
const result = await signIn.create({
identifier: email,
password,
});
if (result.status === "complete") {
await setActive({ session: result.createdSessionId });
}
};
}
JWT Templates
// Get custom JWT for external services
const { getToken } = useAuth();
// Use a JWT template configured in Clerk dashboard
const supabaseToken = await getToken({ template: "supabase" });
const hasuraToken = await getToken({ template: "hasura" });
Configuration
# .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
CLERK_WEBHOOK_SECRET=whsec_...
Troubleshooting
| Issue | Solution |
|---|---|
| Middleware not protecting routes | Verify matcher config; ensure clerkMiddleware is exported as default |
| Hydration mismatch | Wrap auth-dependent content in SignedIn/SignedOut components |
| Token not refreshing | Check getToken() is called fresh before each API request |
| Webhook verification fails | Ensure raw body is passed to Svix; check CLERK_WEBHOOK_SECRET |
| Organization not switching | Call setActive after creating or selecting an organization |
| CORS errors on API routes | Clerk handles CORS; ensure middleware runs on API routes |
| Social login redirect fails | Verify redirect URLs in Clerk dashboard match your app |
| Metadata not updating | Use publicMetadata for client-readable data, privateMetadata for server-only |