clerk next.js
Complete authentication and user management solution for Next.js apps
$ npx docs2skills add clerk-nextjs-authSKILL.md
Clerk
Complete authentication and user management solution for Next.js apps with built-in components, hooks, and server-side utilities.
Quick start
npm install @clerk/nextjs
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
// 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>
)
}
// app/page.tsx
import { SignedIn, SignedOut, SignInButton, UserButton } from "@clerk/nextjs"
export default function Home() {
return (
<div>
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</div>
)
}
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs'
export default function Page() {
return <SignIn />
}
Core concepts
- User: Individual account with profile, authentication methods
- Session: Active authenticated state with JWT tokens
- Organization: Multi-tenant workspace with members and roles
- Client-side: React components and hooks for UI
- Server-side: Middleware and utilities for protected routes
- Webhooks: Real-time sync with your database
Key API surface
Components
import {
SignIn, SignUp, UserProfile, UserButton,
OrganizationProfile, OrganizationSwitcher,
SignedIn, SignedOut, Protect
} from '@clerk/nextjs'
<SignIn routing="path" path="/sign-in" />
<UserButton afterSignOutUrl="/" />
<Protect role="admin">Admin content</Protect>
Hooks
import { useUser, useAuth, useOrganization } from '@clerk/nextjs'
const { user, isLoaded, isSignedIn } = useUser()
const { signOut, getToken } = useAuth()
const { organization, memberships } = useOrganization()
Server utilities
import { auth, currentUser } from '@clerk/nextjs'
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
// Route handler
export async function GET() {
const { userId } = auth()
const user = await currentUser()
}
// Middleware
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)'])
export default clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) auth().protect()
})
Authentication checks
import { auth } from '@clerk/nextjs'
const { userId, sessionClaims, has } = auth()
const canManage = has({ role: 'admin' })
Configuration
Environment variables
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=/dashboard
ClerkProvider options
<ClerkProvider
appearance={{
theme: dark,
variables: { colorPrimary: '#000' }
}}
localization={frFR}
>
Middleware config
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
Common patterns
Protected pages
import { auth } from '@clerk/nextjs'
import { redirect } from 'next/navigation'
export default function Dashboard() {
const { userId } = auth()
if (!userId) redirect('/sign-in')
return <div>Protected content</div>
}
User profile data
import { currentUser } from '@clerk/nextjs'
export default async function Profile() {
const user = await currentUser()
return (
<div>
<p>{user?.emailAddresses[0]?.emailAddress}</p>
<p>{user?.firstName} {user?.lastName}</p>
</div>
)
}
Organization switching
import { OrganizationSwitcher } from '@clerk/nextjs'
<OrganizationSwitcher
hidePersonal
afterCreateOrganizationUrl="/org/:id"
afterSelectOrganizationUrl="/org/:id"
/>
Custom sign-in flow
import { useSignIn } from '@clerk/nextjs'
export default function CustomSignIn() {
const { signIn, isLoaded, setActive } = useSignIn()
const handleSubmit = async (e) => {
const result = await signIn.create({
identifier: email,
password,
})
if (result.status === 'complete') {
await setActive({ session: result.createdSessionId })
}
}
}
JWT token for API calls
import { useAuth } from '@clerk/nextjs'
const { getToken } = useAuth()
const token = await getToken()
fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` }
})
Webhook handling
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix'
export async function POST(req: Request) {
const payload = await req.text()
const signature = req.headers.get('svix-signature')
const webhook = new Webhook(process.env.CLERK_WEBHOOK_SECRET)
const evt = webhook.verify(payload, signature)
if (evt.type === 'user.created') {
// Sync user to database
}
}
Gotchas and best practices
Critical gotchas
- Middleware placement: Must be in root directory, not app/ folder
- Hydration issues: Always check
isLoadedbefore rendering user data - Route protection: Use
auth().protect()in middleware, not just components - Token expiration: JWT tokens auto-refresh, but handle network failures
- Organization context:
useOrganization()only works within OrganizationProvider
Server vs client data
- Server:
auth(),currentUser()- for initial page loads - Client:
useAuth(),useUser()- for interactive components - Never mix server and client utilities in same component
Environment variables
- Publishable key is safe for client-side
- Secret key must stay server-side only
- Use NEXT_PUBLIC_ prefix only for client-accessible vars
Authentication state
isSignedIncan be undefined during loading- Always pair with
isLoadedcheck:
if (!isLoaded) return <Loading />
if (!isSignedIn) return <SignIn />
Middleware configuration
- Matcher is crucial - default protects everything
- Exclude static files:
'/((?!.*\\..*|_next).*)' - API routes need explicit protection
Organizations
- Users can belong to multiple organizations
- Always check active organization:
organization?.id - Roles are organization-scoped, not global
Performance
currentUser()makes API call - use sparinglyauth()is lightweight - just reads session- Pre-load user data in layouts when possible
Custom flows
- Use
create()thenattemptFirstFactor()for sign-in - Check
statusbefore callingsetActive() - Handle
needs_second_factorfor 2FA
Session management
- Sessions auto-extend on activity
signOut()clears all sessions by default- Use
signOut({ sessionId })for specific session
Styling and theming
- Use
appearanceprop for consistent theming - CSS variables override component styles
- Custom pages need manual styling