Clerk Next.js logo

clerk next.js

Complete authentication and user management solution for Next.js apps

$ npx docs2skills add clerk-nextjs-auth
SKILL.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 isLoaded before 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

  • isSignedIn can be undefined during loading
  • Always pair with isLoaded check:
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 sparingly
  • auth() is lightweight - just reads session
  • Pre-load user data in layouts when possible

Custom flows

  • Use create() then attemptFirstFactor() for sign-in
  • Check status before calling setActive()
  • Handle needs_second_factor for 2FA

Session management

  • Sessions auto-extend on activity
  • signOut() clears all sessions by default
  • Use signOut({ sessionId }) for specific session

Styling and theming

  • Use appearance prop for consistent theming
  • CSS variables override component styles
  • Custom pages need manual styling