Clerk Auth Setup in 10 Minutes: 2026 Guide for Indie Hackers
Production-ready Clerk authentication setup with exact code, real error messages, and pitfalls. Get from zero to protected routes in one coffee break.
TL;DR
Clerk handles authentication complexity so you don't. This guide gets you from signup to protected routes in ~10 minutes using Clerk v4+ (verify current version in [official docs](https://clerk.com/docs)). We'll cover Next.js setup, real console errors you'll hit, and production patterns.
Why Clerk?
Managing passwords, email verification, OAuth, and session tokens yourself is a rabbit hole. Clerk abstracts this away without locking you into their ecosystem. You get:
For indie projects, this removes weeks of security-critical code.
Prerequisites
Step 1: Create Clerk Project (2 minutes)
1. Sign up at [clerk.com](https://clerk.com)
2. Create a new application
3. Select your tech stack (we'll use Next.js)
4. Copy your API keys:
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
- CLERK_SECRET_KEY
Paste these into .env.local:
```bash NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_... ```
Important: The NEXT_PUBLIC_ prefix makes the first key safe for browser use. Never expose the secret key.
Step 2: Install Dependencies (1 minute)
For Next.js 13+ with App Router:
```bash npm install @clerk/nextjs ```
Verify in your package.json—we're using @clerk/nextjs v4.x (verify exact version in [official docs](https://clerk.com/docs/quickstarts/nextjs)).
Step 3: Wrap Your App (2 minutes)
Update app/layout.tsx:
```typescript import { ClerkProvider } from '@clerk/nextjs'; import type { Metadata } from 'next';
export const metadata: Metadata = { title: 'My App', };
export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <ClerkProvider> <html lang="en"> <body>{children}</body> </html> </ClerkProvider> ); } ```
If using Pages Router, wrap your _app.tsx instead:
```typescript import { ClerkProvider } from '@clerk/nextjs'; import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) { return ( <ClerkProvider> <Component {...pageProps} /> </ClerkProvider> ); }
export default MyApp; ```
Step 4: Add Auth UI (2 minutes)
Create a simple auth page at app/auth/page.tsx:
```typescript import { SignIn } from '@clerk/nextjs';
export default function AuthPage() { return ( <div style={{ display: 'flex', justifyContent: 'center', paddingTop: '4rem' }}> <SignIn /> </div> ); } ```
For signup, swap <SignIn /> with <SignUp />. Pre-built components handle form validation, OAuth flows, and email verification.
Step 5: Protect Routes (2 minutes)
Create middleware at middleware.ts (root of project, not in app/):
```typescript import { authMiddleware } from '@clerk/nextjs';
export default authMiddleware({ publicRoutes: ['/', '/auth', '/pricing'], ignoredRoutes: ['/api/webhooks/clerk'], });
export const config = { matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'] }; ```
This makes /dashboard and other routes require authentication automatically. Users get redirected to login if unauthenticated.
Step 6: Access User Data (1 minute)
In any component, use the useUser hook:
```typescript 'use client';
import { useUser } from '@clerk/nextjs';
export default function Dashboard() { const { user, isLoaded } = useUser();
if (!isLoaded) return <div>Loading...</div>; if (!user) return <div>Not authenticated</div>;
return ( <div> <h1>Welcome, {user.firstName}</h1> <p>Email: {user.emailAddresses[0]?.emailAddress}</p> <p>User ID: {user.id}</p> </div> ); } ```
For server components, use auth() from @clerk/nextjs:
```typescript import { auth } from '@clerk/nextjs';
export default async function Dashboard() { const { userId, sessionId } = auth();
if (!userId) { return <div>Not authenticated</div>; }
return <div>User ID: {userId}</div>; } ```
Real Console Errors You'll Hit
Error 1: Missing Environment Variables
``` Error: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is not set. Make sure you're in a Next.js app and have the correct environment variable set. ```
Fix: Verify .env.local has both keys, restart dev server (npm run dev).
Error 2: Middleware Not Applied
``` Error: Unexpected redirect at ClerkMiddleware error: NEXT_REDIRECT ```
Fix: Ensure middleware.ts is at project root (same level as app/), not inside app/middleware.ts.
Error 3: useUser Hook in Wrong Context
``` Error: useUser() can only be used in a <ClerkProvider> context. ```
Fix: Add 'use client' at top of component file if it contains hooks.
Production Patterns
Protect API Routes
```typescript import { auth } from '@clerk/nextjs'; import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) { const { userId } = auth();
if (!userId) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); }
// Your logic here return NextResponse.json({ success: true }); } ```
Store Extra User Data
Clerk supports public metadata for user-specific app data:
```typescript import { useUser } from '@clerk/nextjs';
export default function Settings() { const { user } = useUser();
const updateMetadata = async () => { await user?.update({ publicMetadata: { theme: 'dark', plan: 'pro', }, }); };
return <button onClick={updateMetadata}>Save Settings</button>; } ```
Customization
Clerk provides appearance props for styling:
```typescript <SignIn appearance={{ elements: { formButtonPrimary: 'bg-blue-600 hover:bg-blue-700', cardBox: 'shadow-lg', }, }} /> ```
For full control, go headless with the [@clerk/elements](https://clerk.com/docs/elements/overview) API.
Next Steps
What am I missing?
Hit issues? Found a better pattern? Comment below with:
This guide is living documentation—help make it better.