Sr Next Clerk Expert
Senior-level Clerk authentication expertise for Next.js 15/16+ applications.
- Rating
- 4.2 (225 reviews)
- Downloads
- 15,153 downloads
- Version
- 1.0.0
Overview
Senior-level Clerk authentication expertise for Next.js 15/16+ applications.
Complete Documentation
View Source →
Senior Next.js + Clerk Expert
You are a senior engineer implementing Clerk authentication. Follow these patterns exactly—deviations cause production outages.
⚠️ CRITICAL: THE TWELVE COMMANDMENTS
These rules are non-negotiable. Violations cause 500 errors, infinite redirects, and broken sites.
| # | Commandment | Violation Consequence |
|---|---|---|
| I | Use app/(private)/ route groups | Maintenance hell, broken auth |
| II | Keep proxy.ts simple (protect only private) | Every new page needs proxy update |
| III | NEVER call auth() on public pages | 500 errors, slow pages, SEO death |
| IV | Use | Server errors on static pages |
| V | Wrap Clerk components in | Flash of wrong content |
| VI | Pair | Jarring loading states |
| VII | Configure redirects in ClerkProvider | Redirect loops |
| VIII | No handshake redirects on public pages | Broken user experience |
| IX | Keep marketing pages STATIC | Slow pages, bad SEO |
| X | Verify env vars EXACTLY (copy-paste only) | Cryptic 500 errors |
| XI | Use proxy.ts not middleware.ts (Next.js 16+) | Deprecation warnings |
| XII | Test as anonymous user before deploy | Ship broken auth |
Quick Reference
Project Structure
app/
├── (private)/ # Protected - requires auth
│ ├── dashboard/
│ ├── settings/
│ └── layout.tsx # Can call auth() here
├── page.tsx # PUBLIC - NO auth()
├── layout.tsx # Root - ClerkProvider
├── sign-in/[[...sign-in]]/page.tsx
└── sign-up/[[...sign-up]]/page.tsx
The ONLY Correct proxy.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPrivateRoute = createRouteMatcher(["/(private)(.*)"]);
export default clerkMiddleware(async (auth, request) => {
if (isPrivateRoute(request)) {
await auth.protect();
}
});
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)(.*)",
],
};
Patterns by Use Case
Public Page with Auth-Conditional Content
// app/page.tsx - CORRECT
import { ClerkLoaded, ClerkLoading, SignedIn, SignedOut } from "@clerk/nextjs";
export default function HomePage() {
return (
<main>
<h1>Welcome</h1>
<ClerkLoading>
<Skeleton />
</ClerkLoading>
<ClerkLoaded>
<SignedOut>
<a href="/sign-in">Sign In</a>
</SignedOut>
<SignedIn>
<a href="/dashboard">Dashboard</a>
</SignedIn>
</ClerkLoaded>
</main>
);
}
Private Layout (Route Protection)
// app/(private)/layout.tsx
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
export default async function PrivateLayout({ children }: { children: React.ReactNode }) {
const { userId } = await auth();
if (!userId) redirect("/sign-in");
return <>{children}</>;
}
Root Layout with ClerkProvider
// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider
signInUrl="/sign-in"
signUpUrl="/sign-up"
afterSignInUrl="/dashboard"
afterSignUpUrl="/dashboard"
>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
Advanced Patterns
For complex integrations, see reference files:
- Convex Integration: See references/convex.md for ConvexProviderWithClerk patterns
- Stripe/Billing: See references/stripe.md for subscription flows with Clerk
- Multi-tenant/Organizations: See references/organizations.md for org-based auth
- Webhooks: See references/webhooks.md for user sync patterns
- Custom Sign-in Pages: See references/custom-ui.md for branded auth pages
- Debugging Guide: See references/debugging.md for fixing common errors
Environment Variables
# .env.local - COPY FROM CLERK DASHBOARD (do not type manually)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
# Optional redirects
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
⚠️ CRITICAL: Copy-paste keys from Clerk dashboard. Manual typing causes 1/l and x/X errors that produce cryptic 500s.
Common Errors & Fixes
| Error | Cause | Fix |
|---|---|---|
| MIDDLEWARE_INVOCATION_FAILED | Missing/wrong CLERK_SECRET_KEY | Re-copy from dashboard |
| ?__clerk_handshake= in URL | auth() called on public page | Remove auth(), use SignedIn/SignedOut |
| Infinite redirect loop | Missing/wrong redirect config | Set afterSignInUrl in ClerkProvider |
| 500 on homepage | Server-side auth on static page | Make page client-side or remove auth |
| Flash of wrong content | Missing ClerkLoaded wrapper | Wrap Clerk components |
Anti-Patterns (NEVER DO)
// ❌ WRONG - auth() on public page
export default async function HomePage() {
const { userId } = await auth(); // BREAKS STATIC RENDERING
if (userId) redirect("/dashboard");
return <LandingPage />;
}
// ❌ WRONG - listing every public route
const isPublicRoute = createRouteMatcher([
"/", "/about", "/pricing", "/blog", "/contact", // MAINTENANCE HELL
]);
// ❌ WRONG - no ClerkLoaded wrapper
<SignedIn>
<UserButton /> // FLASHES INCORRECTLY
</SignedIn>
// ❌ WRONG - middleware.ts in Next.js 16+
// File: middleware.ts // DEPRECATED - USE proxy.ts
Migration: middleware.ts → proxy.ts
# Option 1: Rename
mv middleware.ts proxy.ts
# Option 2: Codemod
npx @next/codemod@latest middleware-to-proxy
🔐 Security Best Practices
Secret Management
- Store secrets in platform env vars (Vercel, Railway, etc.) — never in code or git
- Use separate keys for dev/staging/prod — Clerk provides different instances
- Rotate keys if compromised — Clerk Dashboard → API Keys → Add new key → update env → delete old
- Limit access — only team members who need keys should have dashboard access
Key Rotation Procedure
- Create new key in Clerk Dashboard
- Update production env var (Vercel:
vercel env rm CLERK_SECRET_KEY production && vercel env add CLERK_SECRET_KEY production) - Redeploy
- Verify auth works
- Delete old key from Clerk Dashboard
Webhook Security
- Always verify signatures — use
svixlibrary (shown in references/webhooks.md) - Use HTTPS endpoints only — never expose webhook URLs over HTTP
- Store CLERK_WEBHOOK_SECRET securely — same as other secrets
Debug Logging
⚠️ NEVER use debug mode in production:// ❌ REMOVE BEFORE DEPLOYING
export default clerkMiddleware(
async (auth, request) => { /* ... */ },
{ debug: true } // LEAKS TOKENS TO LOGS
);
?__clerk_handshake=) which are sensitive. Use only in local development.Least Privilege
| Secret | Scope | Notes |
|---|---|---|
| CLERK_SECRET_KEY | Server only | Never expose to client |
| NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | Client safe | Can be in client bundles |
| CLERK_WEBHOOK_SECRET | Server only | Webhook handler only |
| STRIPE_SECRET_KEY | Server only | API routes only |
Verification Checklist
Before deploying, verify:
- [ ]
proxy.tsexists (notmiddleware.ts) - [ ] proxy.ts ONLY protects
/(private)routes - [ ] No
auth()calls inapp/page.tsxor marketing pages - [ ] All Clerk components wrapped in
- [ ]
shows skeleton/spinner - [ ] Env vars copied exactly from Clerk dashboard
- [ ] Anonymous user can access homepage (incognito test)
- [ ] Sign-in redirects to correct page
- [ ] Dashboard requires authentication
Installation
openclaw install sr-next-clerk-expert
💻Code Examples
### Project Structure
app/
├── (private)/ # Protected - requires auth
│ ├── dashboard/
│ ├── settings/
│ └── layout.tsx # Can call auth() here
├── page.tsx # PUBLIC - NO auth()
├── layout.tsx # Root - ClerkProvider
├── sign-in/[[...sign-in]]/page.tsx
└── sign-up/[[...sign-up]]/page.tsx### The ONLY Correct proxy.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPrivateRoute = createRouteMatcher(["/(private)(.*)"]);
export default clerkMiddleware(async (auth, request) => {
if (isPrivateRoute(request)) {
await auth.protect();
}
});
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)(.*)",
],
};### Public Page with Auth-Conditional Content
// app/page.tsx - CORRECT
import { ClerkLoaded, ClerkLoading, SignedIn, SignedOut } from "@clerk/nextjs";
export default function HomePage() {
return (
<main>
<h1>Welcome</h1>
<ClerkLoading>
<Skeleton />
</ClerkLoading>
<ClerkLoaded>
<SignedOut>
<a href="/sign-in">Sign In</a>
</SignedOut>
<SignedIn>
<a href="/dashboard">Dashboard</a>
</SignedIn>
</ClerkLoaded>
</main>
);
}### Private Layout (Route Protection)
// app/(private)/layout.tsx
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
export default async function PrivateLayout({ children }: { children: React.ReactNode }) {
const { userId } = await auth();
if (!userId) redirect("/sign-in");
return <>{children}</>;
}### Root Layout with ClerkProvider
// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider
signInUrl="/sign-in"
signUpUrl="/sign-up"
afterSignInUrl="/dashboard"
afterSignUpUrl="/dashboard"
>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
**⚠️ CRITICAL**: Copy-paste keys from Clerk dashboard. Manual typing causes `1/l` and `x/X` errors that produce cryptic 500s.
---
## Common Errors & Fixes
| Error | Cause | Fix |
|-------|-------|-----|
| `MIDDLEWARE_INVOCATION_FAILED` | Missing/wrong CLERK_SECRET_KEY | Re-copy from dashboard |
| `?__clerk_handshake=` in URL | `auth()` called on public page | Remove auth(), use SignedIn/SignedOut |
| Infinite redirect loop | Missing/wrong redirect config | Set afterSignInUrl in ClerkProvider |
| 500 on homepage | Server-side auth on static page | Make page client-side or remove auth |
| Flash of wrong content | Missing ClerkLoaded wrapper | Wrap Clerk components |
---
## Anti-Patterns (NEVER DO)// File: middleware.ts // DEPRECATED - USE proxy.ts
---
## Migration: middleware.ts → proxy.tsnpx @next/codemod@latest middleware-to-proxy
---
## 🔐 Security Best Practices
### Secret Management
- **Store secrets in platform env vars** (Vercel, Railway, etc.) — never in code or git
- **Use separate keys for dev/staging/prod** — Clerk provides different instances
- **Rotate keys if compromised** — Clerk Dashboard → API Keys → Add new key → update env → delete old
- **Limit access** — only team members who need keys should have dashboard access
### Key Rotation Procedure
1. Create new key in Clerk Dashboard
2. Update production env var (Vercel: `vercel env rm CLERK_SECRET_KEY production && vercel env add CLERK_SECRET_KEY production`)
3. Redeploy
4. Verify auth works
5. Delete old key from Clerk Dashboard
### Webhook Security
- **Always verify signatures** — use `svix` library (shown in references/webhooks.md)
- **Use HTTPS endpoints only** — never expose webhook URLs over HTTP
- **Store CLERK_WEBHOOK_SECRET securely** — same as other secrets
### Debug Logging
⚠️ **NEVER use debug mode in production:**# .env.local - COPY FROM CLERK DASHBOARD (do not type manually)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
# Optional redirects
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// ❌ WRONG - auth() on public page
export default async function HomePage() {
const { userId } = await auth(); // BREAKS STATIC RENDERING
if (userId) redirect("/dashboard");
return <LandingPage />;
}
// ❌ WRONG - listing every public route
const isPublicRoute = createRouteMatcher([
"/", "/about", "/pricing", "/blog", "/contact", // MAINTENANCE HELL
]);
// ❌ WRONG - no ClerkLoaded wrapper
<SignedIn>
<UserButton /> // FLASHES INCORRECTLY
</SignedIn>
// ❌ WRONG - middleware.ts in Next.js 16+
// File: middleware.ts // DEPRECATED - USE proxy.tsTags
Quick Info
Ready to Install?
Get started with this skill in seconds
Related Skills
4claw
4claw — a moderated imageboard for AI agents.
Aap Passport
Agent Attestation Protocol - The Reverse Turing Test.
Acestep Lyrics Transcription
Transcribe audio to timestamped lyrics using OpenAI Whisper or ElevenLabs Scribe API.
Adaptive Suite
A continuously adaptive skill suite that empowers Clawdbot.