Claude-skill-registry clerk
Implements authentication with Clerk including user management, protected routes, middleware, and React components. Use when adding authentication, managing users, protecting routes, or implementing sign-in/sign-up flows.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clerk-mgd34msu-goodvibes-gemini" ~/.claude/skills/majiayu000-claude-skill-registry-clerk-9a810a && rm -rf "$T"
manifest:
skills/data/clerk-mgd34msu-goodvibes-gemini/SKILL.mdsource content
Clerk
Complete authentication and user management platform for modern web applications.
Quick Start
Install:
npm install @clerk/nextjs
Environment variables:
# .env.local NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_...
Middleware Setup
// middleware.ts (or proxy.ts for Next.js 15+) import { clerkMiddleware } from '@clerk/nextjs/server'; export default clerkMiddleware(); export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', '/(api|trpc)(.*)', ], };
Provider 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> ); }
UI Components
Auth Buttons
import { SignInButton, SignUpButton, SignedIn, SignedOut, UserButton, } from '@clerk/nextjs'; export function Header() { return ( <header className="flex justify-between items-center p-4"> <h1>My App</h1> <div className="flex gap-4"> <SignedOut> <SignInButton mode="modal" /> <SignUpButton mode="modal" /> </SignedOut> <SignedIn> <UserButton afterSignOutUrl="/" /> </SignedIn> </div> </header> ); }
Custom Sign-In Page
// app/sign-in/[[...sign-in]]/page.tsx import { SignIn } from '@clerk/nextjs'; export default function SignInPage() { return ( <div className="flex justify-center items-center min-h-screen"> <SignIn appearance={{ elements: { rootBox: 'mx-auto', card: 'shadow-xl', }, }} /> </div> ); }
Custom Sign-Up Page
// app/sign-up/[[...sign-up]]/page.tsx import { SignUp } from '@clerk/nextjs'; export default function SignUpPage() { return ( <div className="flex justify-center items-center min-h-screen"> <SignUp /> </div> ); }
Route Protection
Using createRouteMatcher
// middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; const isProtectedRoute = createRouteMatcher([ '/dashboard(.*)', '/settings(.*)', '/api/private(.*)', ]); const isPublicRoute = createRouteMatcher([ '/', '/sign-in(.*)', '/sign-up(.*)', '/api/public(.*)', ]); export default clerkMiddleware(async (auth, req) => { if (isProtectedRoute(req)) { await auth.protect(); } });
Protect All Routes
// middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; const isPublicRoute = createRouteMatcher([ '/', '/sign-in(.*)', '/sign-up(.*)', ]); export default clerkMiddleware(async (auth, req) => { if (!isPublicRoute(req)) { await auth.protect(); } });
Role-Based Protection
// middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; const isAdminRoute = createRouteMatcher(['/admin(.*)']); export default clerkMiddleware(async (auth, req) => { if (isAdminRoute(req)) { await auth.protect((has) => { return has({ role: 'org:admin' }); }); } });
Permission-Based Protection
export default clerkMiddleware(async (auth, req) => { if (isProtectedRoute(req)) { await auth.protect((has) => { return has({ permission: 'org:billing:manage' }); }); } });
React Hooks
useUser
'use client'; import { useUser } from '@clerk/nextjs'; export function Profile() { const { isLoaded, isSignedIn, user } = useUser(); if (!isLoaded) { return <div>Loading...</div>; } if (!isSignedIn) { return <div>Please sign in</div>; } return ( <div> <h1>Hello, {user.firstName}!</h1> <p>Email: {user.primaryEmailAddress?.emailAddress}</p> <img src={user.imageUrl} alt="Profile" className="w-16 h-16 rounded-full" /> </div> ); }
useAuth
'use client'; import { useAuth } from '@clerk/nextjs'; export function AuthInfo() { const { isLoaded, userId, sessionId, getToken } = useAuth(); if (!isLoaded) { return <div>Loading...</div>; } if (!userId) { return <div>Not signed in</div>; } return ( <div> <p>User ID: {userId}</p> <p>Session ID: {sessionId}</p> </div> ); } // Get token for API calls async function fetchWithAuth() { const { getToken } = useAuth(); const token = await getToken(); const res = await fetch('/api/protected', { headers: { Authorization: `Bearer ${token}`, }, }); }
useClerk
'use client'; import { useClerk } from '@clerk/nextjs'; export function CustomSignOut() { const { signOut, openSignIn, openUserProfile } = useClerk(); return ( <div className="flex gap-2"> <button onClick={() => openSignIn()}>Sign In</button> <button onClick={() => openUserProfile()}>Profile</button> <button onClick={() => signOut()}>Sign Out</button> </div> ); }
useOrganization
'use client'; import { useOrganization } from '@clerk/nextjs'; export function OrgInfo() { const { isLoaded, organization, membership } = useOrganization(); if (!isLoaded) return <div>Loading...</div>; if (!organization) return <div>No organization selected</div>; return ( <div> <h2>{organization.name}</h2> <p>Role: {membership?.role}</p> <p>Members: {organization.membersCount}</p> </div> ); }
Server-Side Auth
Server Components
// app/dashboard/page.tsx import { currentUser, auth } from '@clerk/nextjs/server'; export default async function DashboardPage() { const user = await currentUser(); if (!user) { return <div>Please sign in</div>; } return ( <div> <h1>Welcome, {user.firstName}!</h1> <p>Email: {user.emailAddresses[0].emailAddress}</p> </div> ); } // Using auth() for session data export default async function ProtectedPage() { const { userId, sessionClaims } = await auth(); if (!userId) { return <div>Unauthorized</div>; } return <div>User ID: {userId}</div>; }
API Routes
// app/api/user/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({ id: userId, email: user?.emailAddresses[0].emailAddress, }); }
Server Actions
// app/actions.ts 'use server'; import { auth, currentUser } from '@clerk/nextjs/server'; export async function updateProfile(formData: FormData) { const { userId } = await auth(); if (!userId) { throw new Error('Unauthorized'); } const name = formData.get('name') as string; // Update user in database await db.user.update({ where: { clerkId: userId }, data: { name }, }); return { success: true }; }
User Metadata
Public Metadata (Read-only from client)
// Server-side: Update public metadata import { clerkClient } from '@clerk/nextjs/server'; await clerkClient.users.updateUserMetadata(userId, { publicMetadata: { role: 'admin', plan: 'premium', }, });
Private Metadata (Server-only)
// Only accessible on server await clerkClient.users.updateUserMetadata(userId, { privateMetadata: { stripeCustomerId: 'cus_...', internalNotes: 'VIP customer', }, });
Unsafe Metadata (Client-writable)
'use client'; import { useUser } from '@clerk/nextjs'; export function UpdatePreferences() { const { user } = useUser(); async function updateTheme(theme: string) { await user?.update({ unsafeMetadata: { theme, notifications: true, }, }); } return ( <button onClick={() => updateTheme('dark')}> Set Dark Theme </button> ); }
Webhooks
// app/api/webhooks/clerk/route.ts import { Webhook } from 'svix'; import { headers } from 'next/headers'; import { WebhookEvent } from '@clerk/nextjs/server'; export async function POST(req: Request) { const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET!; const headerPayload = 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(WEBHOOK_SECRET); let evt: WebhookEvent; try { evt = wh.verify(body, { 'svix-id': svix_id, 'svix-timestamp': svix_timestamp, 'svix-signature': svix_signature, }) as WebhookEvent; } catch (err) { return new Response('Invalid signature', { status: 400 }); } const eventType = evt.type; if (eventType === 'user.created') { const { id, email_addresses, first_name, last_name } = evt.data; await db.user.create({ data: { clerkId: id, email: email_addresses[0].email_address, firstName: first_name, lastName: last_name, }, }); } if (eventType === 'user.updated') { const { id, first_name, last_name } = evt.data; await db.user.update({ where: { clerkId: id }, data: { firstName: first_name, lastName: last_name }, }); } if (eventType === 'user.deleted') { const { id } = evt.data; await db.user.delete({ where: { clerkId: id }, }); } return new Response('OK', { status: 200 }); }
Theming
// app/layout.tsx import { ClerkProvider } from '@clerk/nextjs'; import { dark } from '@clerk/themes'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <ClerkProvider appearance={{ baseTheme: dark, variables: { colorPrimary: '#3b82f6', colorBackground: '#1f2937', }, elements: { card: 'shadow-xl rounded-xl', formButtonPrimary: 'bg-blue-500 hover:bg-blue-600', footerActionLink: 'text-blue-400 hover:text-blue-300', }, }} > <html lang="en"> <body>{children}</body> </html> </ClerkProvider> ); }
Best Practices
- Use middleware for route protection - Centralized, secure
- Sync users via webhooks - Keep database in sync
- Store user IDs, not emails - Emails can change
- Use public metadata for roles - Accessible client-side
- Leverage organizations - For multi-tenant apps
Common Mistakes
| Mistake | Fix |
|---|---|
| Missing middleware | Add clerkMiddleware() |
| Unprotected API routes | Check auth() in routes |
| Client-side role checks only | Validate on server |
| Hardcoded redirect URLs | Use environment variables |
| Missing webhook verification | Always verify signatures |
Reference Files
- references/middleware.md - Advanced middleware patterns
- references/organizations.md - Multi-tenant auth
- references/webhooks.md - Event handling