Validates Clerk authentication in Next.js apps, detecting deprecated patterns like authMiddleware, ensuring proxy.ts (Next.js 16), ClerkProvider setup, modern imports, and env vars. Use before Clerk setup or auditing.
npx claudepluginhub shipshitdev/libraryThis skill uses the workspace's default tool permissions.
Validates Clerk authentication configuration and prevents deprecated patterns. AI assistants often generate old Clerk patterns - this skill enforces modern Clerk with Next.js 16.
Validates Clerk authentication in Next.js/NestJS apps: checks package versions, proxy.ts usage (Next.js 16), ClerkProvider setup, auth imports, env vars, and deprecated patterns.
Secures Clerk authentication in Next.js apps with env var protection, hardened middleware, secure API routes, and security headers.
Provides expert patterns for Clerk auth in Next.js App Router: providers, middleware, route protection, sign-in/up pages, organizations, webhooks, user sync. Use for secure auth setup.
Share bugs, ideas, or general feedback.
Validates Clerk authentication configuration and prevents deprecated patterns. AI assistants often generate old Clerk patterns - this skill enforces modern Clerk with Next.js 16.
python3 ~/.claude/skills/clerk-validator/scripts/validate.py --root .
python3 ~/.claude/skills/clerk-validator/scripts/validate.py --root . --strict
// GOOD: Latest Clerk
"@clerk/nextjs": "^6.0.0"
// BAD: Old version
"@clerk/nextjs": "^4.0.0"
GOOD - Next.js 16:
// proxy.ts
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
BAD - Deprecated:
// middleware.ts (deprecated in Next.js 16)
import { authMiddleware } from "@clerk/nextjs"; // DEPRECATED
export default authMiddleware();
GOOD:
// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
export default function RootLayout({ children }) {
return (
<ClerkProvider>
<html>
<body>{children}</body>
</html>
</ClerkProvider>
);
}
BAD - Missing or wrong location:
// Don't put in _app.tsx (Pages Router deprecated)
// Don't forget to wrap the entire app
GOOD - Server-side:
import { auth } from "@clerk/nextjs/server";
export default async function Page() {
const { userId } = await auth();
// ...
}
BAD - Old patterns:
// Don't use
import { getAuth } from "@clerk/nextjs/server"; // OLD
import { currentUser } from "@clerk/nextjs"; // Check version
Required:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
Optional but recommended:
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=/onboarding
| Deprecated | Replacement |
|---|---|
authMiddleware() | clerkMiddleware() |
middleware.ts | proxy.ts (Next.js 16) |
getAuth() | auth() |
@clerk/nextjs < v5 | @clerk/nextjs@latest |
_app.tsx provider | app/layout.tsx provider |
withClerkMiddleware | clerkMiddleware() |
=== Clerk Validation Report ===
Package Version: @clerk/nextjs@6.0.0 ✓
Configuration:
✓ ClerkProvider in app/layout.tsx
✓ proxy.ts with clerkMiddleware
✗ Found middleware.ts - should use proxy.ts for Next.js 16
✓ Environment variables configured
Auth Patterns:
✓ Using auth() from @clerk/nextjs/server
✗ Found deprecated authMiddleware() in 1 file
Summary: 2 issues found
// app/dashboard/page.tsx
import { auth } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const { userId } = await auth();
if (!userId) {
redirect("/sign-in");
}
return <Dashboard />;
}
"use client";
import { useAuth } from "@clerk/nextjs";
export default function ProtectedComponent() {
const { isLoaded, userId } = useAuth();
if (!isLoaded) return <Loading />;
if (!userId) return <Redirect to="/sign-in" />;
return <Content />;
}
// app/api/protected/route.ts
import { auth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
export async function GET() {
const { userId } = await auth();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}
return NextResponse.json({ userId });
}
// auth/clerk.guard.ts
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { clerkClient } from "@clerk/clerk-sdk-node";
@Injectable()
export class ClerkGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
if (!token) return false;
try {
const { userId } = await clerkClient.verifyToken(token);
request.userId = userId;
return true;
} catch {
return false;
}
}
private extractToken(request: any): string | null {
const auth = request.headers.authorization;
if (!auth?.startsWith("Bearer ")) return null;
return auth.slice(7);
}
}
// 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;
if (!WEBHOOK_SECRET) throw new Error("Missing 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");
const body = await req.text();
const wh = new Webhook(WEBHOOK_SECRET);
const evt = wh.verify(body, {
"svix-id": svix_id!,
"svix-timestamp": svix_timestamp!,
"svix-signature": svix_signature!,
}) as WebhookEvent;
// Handle event
switch (evt.type) {
case "user.created":
// Sync to database
break;
}
return new Response("OK", { status: 200 });
}
# .github/workflows/validate.yml
- name: Validate Clerk Config
run: |
python3 ~/.claude/skills/clerk-validator/scripts/validate.py \
--root . \
--strict \
--ci
nextjs-validator - Validates Next.js 16 (proxy.ts)biome-validator - Validates linting configgit-safety - Ensures no secrets committed