[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-7a2730a4-040a-41e9-bdeb-80bbcb2cef6d":3,"$fhY6VSl7CncJQ3vrqxG7OZglG78JnLQFQQEj45TRK3l0":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"7a2730a4-040a-41e9-bdeb-80bbcb2cef6d","clerk-auth","专家级模式：职员认证实现、中间件","cat_life_career","mod_other","sickn33,other","---\nname: clerk-auth\ndescription: Expert patterns for Clerk auth implementation, middleware,\n  organizations, webhooks, and user sync\nrisk: safe\nsource: vibeship-spawner-skills (Apache 2.0)\ndate_added: 2026-02-27\n---\n\n# Clerk Authentication\n\nExpert patterns for Clerk auth implementation, middleware, organizations, webhooks, and user sync\n\n## Patterns\n\n### Next.js App Router Setup\n\nComplete Clerk setup for Next.js 14\u002F15 App Router.\n\nIncludes ClerkProvider, environment variables, and basic\nsign-in\u002Fsign-up components.\n\nKey components:\n- ClerkProvider: Wraps app for auth context\n- \u003CSignIn \u002F>, \u003CSignUp \u002F>: Pre-built auth forms\n- \u003CUserButton \u002F>: User menu with session management\n\n### Code_example\n\n# Environment variables (.env.local)\nNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...\nCLERK_SECRET_KEY=sk_test_...\nNEXT_PUBLIC_CLERK_SIGN_IN_URL=\u002Fsign-in\nNEXT_PUBLIC_CLERK_SIGN_UP_URL=\u002Fsign-up\nNEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=\u002Fdashboard\nNEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=\u002Fonboarding\n\n\u002F\u002F app\u002Flayout.tsx\nimport { ClerkProvider } from '@clerk\u002Fnextjs';\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    \u003CClerkProvider>\n      \u003Chtml lang=\"en\">\n        \u003Cbody>{children}\u003C\u002Fbody>\n      \u003C\u002Fhtml>\n    \u003C\u002FClerkProvider>\n  );\n}\n\n\u002F\u002F app\u002Fsign-in\u002F[[...sign-in]]\u002Fpage.tsx\nimport { SignIn } from '@clerk\u002Fnextjs';\n\nexport default function SignInPage() {\n  return (\n    \u003Cdiv className=\"flex justify-center items-center min-h-screen\">\n      \u003CSignIn \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F app\u002Fsign-up\u002F[[...sign-up]]\u002Fpage.tsx\nimport { SignUp } from '@clerk\u002Fnextjs';\n\nexport default function SignUpPage() {\n  return (\n    \u003Cdiv className=\"flex justify-center items-center min-h-screen\">\n      \u003CSignUp \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F components\u002FHeader.tsx\nimport { SignedIn, SignedOut, SignInButton, UserButton } from '@clerk\u002Fnextjs';\n\nexport function Header() {\n  return (\n    \u003Cheader className=\"flex justify-between p-4\">\n      \u003Ch1>My App\u003C\u002Fh1>\n      \u003CSignedOut>\n        \u003CSignInButton \u002F>\n      \u003C\u002FSignedOut>\n      \u003CSignedIn>\n        \u003CUserButton afterSignOutUrl=\"\u002F\" \u002F>\n      \u003C\u002FSignedIn>\n    \u003C\u002Fheader>\n  );\n}\n\n### Anti_patterns\n\n- Pattern: ClerkProvider inside page component | Why: Provider must wrap entire app in root layout | Fix: Move ClerkProvider to app\u002Flayout.tsx\n- Pattern: Using auth() without middleware | Why: auth() requires clerkMiddleware to be configured | Fix: Set up middleware.ts with clerkMiddleware\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Fnextjs\u002Fgetting-started\u002Fquickstart\n\n### Middleware Route Protection\n\nProtect routes using clerkMiddleware and createRouteMatcher.\n\nBest practices:\n- Single middleware.ts file at project root\n- Use createRouteMatcher for route groups\n- auth.protect() for explicit protection\n- Centralize all auth logic in middleware\n\n### Code_example\n\n\u002F\u002F middleware.ts\nimport { clerkMiddleware, createRouteMatcher } from '@clerk\u002Fnextjs\u002Fserver';\n\n\u002F\u002F Define protected route patterns\nconst isProtectedRoute = createRouteMatcher([\n  '\u002Fdashboard(.*)',\n  '\u002Fsettings(.*)',\n  '\u002Fapi\u002Fprivate(.*)',\n]);\n\n\u002F\u002F Define public routes (optional, for clarity)\nconst isPublicRoute = createRouteMatcher([\n  '\u002F',\n  '\u002Fsign-in(.*)',\n  '\u002Fsign-up(.*)',\n  '\u002Fapi\u002Fwebhooks(.*)',\n]);\n\nexport default clerkMiddleware(async (auth, req) => {\n  \u002F\u002F Protect matched routes\n  if (isProtectedRoute(req)) {\n    await auth.protect();\n  }\n});\n\nexport const config = {\n  matcher: [\n    \u002F\u002F Match all routes except static files\n    '\u002F((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',\n    \u002F\u002F Always run for API routes\n    '\u002F(api|trpc)(.*)',\n  ],\n};\n\n\u002F\u002F Advanced: Role-based protection\nexport default clerkMiddleware(async (auth, req) => {\n  if (isProtectedRoute(req)) {\n    await auth.protect();\n  }\n\n  \u002F\u002F Admin routes require admin role\n  if (req.nextUrl.pathname.startsWith('\u002Fadmin')) {\n    await auth.protect({\n      role: 'org:admin',\n    });\n  }\n\n  \u002F\u002F Premium routes require premium permission\n  if (req.nextUrl.pathname.startsWith('\u002Fpremium')) {\n    await auth.protect({\n      permission: 'org:premium:access',\n    });\n  }\n});\n\n### Anti_patterns\n\n- Pattern: Multiple middleware.ts files | Why: Causes conflicts and redirect loops | Fix: Use single middleware.ts with route matchers\n- Pattern: Manual redirects in components | Why: Double redirects, missed routes | Fix: Handle all redirects in middleware\n- Pattern: Missing matcher config | Why: Middleware won't run on all routes | Fix: Add comprehensive matcher pattern\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Freference\u002Fnextjs\u002Fclerk-middleware\n\n### Server Component Authentication\n\nAccess auth state in Server Components using auth() and currentUser().\n\nKey functions:\n- auth(): Returns userId, sessionId, orgId, claims\n- currentUser(): Returns full User object\n- Both require clerkMiddleware to be configured\n\n### Code_example\n\n\u002F\u002F app\u002Fdashboard\u002Fpage.tsx (Server Component)\nimport { auth, currentUser } from '@clerk\u002Fnextjs\u002Fserver';\nimport { redirect } from 'next\u002Fnavigation';\n\nexport default async function DashboardPage() {\n  const { userId } = await auth();\n\n  if (!userId) {\n    redirect('\u002Fsign-in');\n  }\n\n  \u002F\u002F Full user data (counts toward rate limits)\n  const user = await currentUser();\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>Welcome, {user?.firstName}!\u003C\u002Fh1>\n      \u003Cp>Email: {user?.emailAddresses[0]?.emailAddress}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Using auth() for quick checks\nexport default async function ProtectedLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  const { userId, orgId, orgRole } = await auth();\n\n  if (!userId) {\n    redirect('\u002Fsign-in');\n  }\n\n  \u002F\u002F Check organization access\n  if (!orgId) {\n    redirect('\u002Fselect-org');\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Organization Role: {orgRole}\u003C\u002Fp>\n      {children}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Server Action with auth check\n\u002F\u002F app\u002Factions\u002Fposts.ts\n'use server';\nimport { auth } from '@clerk\u002Fnextjs\u002Fserver';\n\nexport async function createPost(formData: FormData) {\n  const { userId } = await auth();\n\n  if (!userId) {\n    throw new Error('Unauthorized');\n  }\n\n  const title = formData.get('title') as string;\n\n  \u002F\u002F Create post with userId\n  const post = await prisma.post.create({\n    data: {\n      title,\n      authorId: userId,\n    },\n  });\n\n  return post;\n}\n\n### Anti_patterns\n\n- Pattern: Not awaiting auth() | Why: auth() is async in App Router | Fix: Use await auth() or const { userId } = await auth()\n- Pattern: Using currentUser() for simple checks | Why: Counts toward rate limits, slower than auth() | Fix: Use auth() for userId checks, currentUser() for user data\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Freferences\u002Fnextjs\u002Fauth\n\n### Client Component Hooks\n\nAccess auth state in Client Components using hooks.\n\nKey hooks:\n- useUser(): User object and loading state\n- useAuth(): Auth state, signOut, etc.\n- useSession(): Session object\n- useOrganization(): Current organization\n\n### Code_example\n\n\u002F\u002F components\u002FUserProfile.tsx\n'use client';\nimport { useUser, useAuth } from '@clerk\u002Fnextjs';\n\nexport function UserProfile() {\n  const { user, isLoaded, isSignedIn } = useUser();\n  const { signOut } = useAuth();\n\n  if (!isLoaded) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  if (!isSignedIn) {\n    return \u003Cdiv>Not signed in\u003C\u002Fdiv>;\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cimg src={user.imageUrl} alt={user.fullName ?? ''} \u002F>\n      \u003Ch2>{user.fullName}\u003C\u002Fh2>\n      \u003Cp>{user.emailAddresses[0]?.emailAddress}\u003C\u002Fp>\n      \u003Cbutton onClick={() => signOut()}>Sign Out\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Organization context\n'use client';\nimport { useOrganization, useOrganizationList } from '@clerk\u002Fnextjs';\n\nexport function OrgSwitcher() {\n  const { organization, membership } = useOrganization();\n  const { setActive, userMemberships } = useOrganizationList({\n    userMemberships: { infinite: true },\n  });\n\n  if (!organization) {\n    return \u003Cp>No organization selected\u003C\u002Fp>;\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Current: {organization.name}\u003C\u002Fp>\n      \u003Cp>Role: {membership?.role}\u003C\u002Fp>\n\n      \u003Cselect\n        onChange={(e) => setActive?.({ organization: e.target.value })}\n        value={organization.id}\n      >\n        {userMemberships.data?.map((mem) => (\n          \u003Coption key={mem.organization.id} value={mem.organization.id}>\n            {mem.organization.name}\n          \u003C\u002Foption>\n        ))}\n      \u003C\u002Fselect>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Protected client component\n'use client';\nimport { useAuth } from '@clerk\u002Fnextjs';\nimport { useRouter } from 'next\u002Fnavigation';\nimport { useEffect } from 'react';\n\nexport function ProtectedContent() {\n  const { isLoaded, userId } = useAuth();\n  const router = useRouter();\n\n  useEffect(() => {\n    if (isLoaded && !userId) {\n      router.push('\u002Fsign-in');\n    }\n  }, [isLoaded, userId, router]);\n\n  if (!isLoaded || !userId) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return \u003Cdiv>Protected content here\u003C\u002Fdiv>;\n}\n\n### Anti_patterns\n\n- Pattern: Not checking isLoaded | Why: Auth state undefined during hydration | Fix: Always check isLoaded before accessing user\u002Fauth state\n- Pattern: Using hooks in Server Components | Why: Hooks only work in Client Components | Fix: Use auth() and currentUser() in Server Components\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Freferences\u002Freact\u002Fuse-user\n\n### Organizations and Multi-Tenancy\n\nImplement B2B multi-tenancy with Clerk Organizations.\n\nFeatures:\n- Multiple orgs per user\n- Roles and permissions\n- Organization-scoped data\n- Enterprise SSO per organization\n\n### Code_example\n\n\u002F\u002F Organization creation UI\n\u002F\u002F app\u002Fcreate-org\u002Fpage.tsx\nimport { CreateOrganization } from '@clerk\u002Fnextjs';\n\nexport default function CreateOrgPage() {\n  return (\n    \u003Cdiv className=\"flex justify-center\">\n      \u003CCreateOrganization afterCreateOrganizationUrl=\"\u002Fdashboard\" \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Organization profile and management\n\u002F\u002F app\u002Forg-settings\u002Fpage.tsx\nimport { OrganizationProfile } from '@clerk\u002Fnextjs';\n\nexport default function OrgSettingsPage() {\n  return \u003COrganizationProfile \u002F>;\n}\n\n\u002F\u002F Organization switcher in header\n\u002F\u002F components\u002FHeader.tsx\nimport { OrganizationSwitcher, UserButton } from '@clerk\u002Fnextjs';\n\nexport function Header() {\n  return (\n    \u003Cheader className=\"flex justify-between p-4\">\n      \u003COrganizationSwitcher\n        hidePersonal\n        afterCreateOrganizationUrl=\"\u002Fdashboard\"\n        afterSelectOrganizationUrl=\"\u002Fdashboard\"\n      \u002F>\n      \u003CUserButton \u002F>\n    \u003C\u002Fheader>\n  );\n}\n\n\u002F\u002F Org-scoped data access\n\u002F\u002F app\u002Fdashboard\u002Fpage.tsx\nimport { auth } from '@clerk\u002Fnextjs\u002Fserver';\nimport { prisma } from '@\u002Flib\u002Fprisma';\n\nexport default async function DashboardPage() {\n  const { orgId } = await auth();\n\n  if (!orgId) {\n    redirect('\u002Fselect-org');\n  }\n\n  \u002F\u002F Fetch org-scoped data\n  const projects = await prisma.project.findMany({\n    where: { organizationId: orgId },\n  });\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>Projects\u003C\u002Fh1>\n      {projects.map((p) => (\n        \u003Cdiv key={p.id}>{p.name}\u003C\u002Fdiv>\n      ))}\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Role-based UI\n'use client';\nimport { useOrganization, Protect } from '@clerk\u002Fnextjs';\n\nexport function AdminPanel() {\n  const { membership } = useOrganization();\n\n  \u002F\u002F Using Protect component\n  return (\n    \u003CProtect role=\"org:admin\" fallback={\u003Cp>Admin access required\u003C\u002Fp>}>\n      \u003Cdiv>Admin content here\u003C\u002Fdiv>\n    \u003C\u002FProtect>\n  );\n\n  \u002F\u002F Or manual check\n  if (membership?.role !== 'org:admin') {\n    return \u003Cp>Admin access required\u003C\u002Fp>;\n  }\n\n  return \u003Cdiv>Admin content here\u003C\u002Fdiv>;\n}\n\n### Anti_patterns\n\n- Pattern: Not scoping data by orgId | Why: Data leaks between organizations | Fix: Always filter queries by orgId from auth()\n- Pattern: Hardcoding role strings | Why: Typos cause access issues | Fix: Define role constants or use TypeScript enums\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Fguides\u002Forganizations\n- https:\u002F\u002Fclerk.com\u002Farticles\u002Fmulti-tenancy-in-react-applications-guide\n\n### Webhook User Sync\n\nSync Clerk users to your database using webhooks.\n\nKey webhooks:\n- user.created: New user signed up\n- user.updated: User profile changed\n- user.deleted: User deleted account\n\nUses svix for signature verification.\n\n### Code_example\n\n\u002F\u002F app\u002Fapi\u002Fwebhooks\u002Fclerk\u002Froute.ts\nimport { Webhook } from 'svix';\nimport { headers } from 'next\u002Fheaders';\nimport { WebhookEvent } from '@clerk\u002Fnextjs\u002Fserver';\nimport { prisma } from '@\u002Flib\u002Fprisma';\n\nexport async function POST(req: Request) {\n  const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;\n\n  if (!WEBHOOK_SECRET) {\n    throw new Error('Missing CLERK_WEBHOOK_SECRET');\n  }\n\n  \u002F\u002F Get headers\n  const headerPayload = await headers();\n  const svix_id = headerPayload.get('svix-id');\n  const svix_timestamp = headerPayload.get('svix-timestamp');\n  const svix_signature = headerPayload.get('svix-signature');\n\n  if (!svix_id || !svix_timestamp || !svix_signature) {\n    return new Response('Missing svix headers', { status: 400 });\n  }\n\n  \u002F\u002F Get body\n  const payload = await req.json();\n  const body = JSON.stringify(payload);\n\n  \u002F\u002F Verify webhook\n  const wh = new Webhook(WEBHOOK_SECRET);\n  let evt: WebhookEvent;\n\n  try {\n    evt = wh.verify(body, {\n      'svix-id': svix_id,\n      'svix-timestamp': svix_timestamp,\n      'svix-signature': svix_signature,\n    }) as WebhookEvent;\n  } catch (err) {\n    console.error('Webhook verification failed:', err);\n    return new Response('Verification failed', { status: 400 });\n  }\n\n  \u002F\u002F Handle events\n  const eventType = evt.type;\n\n  if (eventType === 'user.created') {\n    const { id, email_addresses, first_name, last_name, image_url } = evt.data;\n\n    await prisma.user.create({\n      data: {\n        clerkId: id,\n        email: email_addresses[0]?.email_address,\n        firstName: first_name,\n        lastName: last_name,\n        imageUrl: image_url,\n      },\n    });\n  }\n\n  if (eventType === 'user.updated') {\n    const { id, email_addresses, first_name, last_name, image_url } = evt.data;\n\n    await prisma.user.update({\n      where: { clerkId: id },\n      data: {\n        email: email_addresses[0]?.email_address,\n        firstName: first_name,\n        lastName: last_name,\n        imageUrl: image_url,\n      },\n    });\n  }\n\n  if (eventType === 'user.deleted') {\n    const { id } = evt.data;\n\n    await prisma.user.delete({\n      where: { clerkId: id! },\n    });\n  }\n\n  return new Response('Webhook processed', { status: 200 });\n}\n\n\u002F\u002F Prisma schema\n\u002F\u002F prisma\u002Fschema.prisma\nmodel User {\n  id        String   @id @default(cuid())\n  clerkId   String   @unique\n  email     String   @unique\n  firstName String?\n  lastName  String?\n  imageUrl  String?\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n\n  posts     Post[]\n  @@index([clerkId])\n}\n\n### Anti_patterns\n\n- Pattern: Not verifying webhook signature | Why: Anyone can hit your endpoint with fake data | Fix: Always verify with svix\n- Pattern: Blocking middleware for webhook routes | Why: Webhooks come from Clerk, not authenticated users | Fix: Add \u002Fapi\u002Fwebhooks(.*)' to public routes\n- Pattern: Not handling race conditions | Why: user.created might arrive after user.updated | Fix: Use upsert instead of create, handle missing records\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Fwebhooks\u002Fsync-data\n- https:\u002F\u002Fclerk.com\u002Farticles\u002Fhow-to-sync-clerk-user-data-to-your-database\n\n### API Route Protection\n\nProtect API routes using auth() from Clerk.\n\nRoute Handlers in App Router use auth() for authentication.\nMiddleware provides initial protection, auth() provides in-handler verification.\n\n### Code_example\n\n\u002F\u002F app\u002Fapi\u002Fprojects\u002Froute.ts\nimport { auth } from '@clerk\u002Fnextjs\u002Fserver';\nimport { prisma } from '@\u002Flib\u002Fprisma';\nimport { NextResponse } from 'next\u002Fserver';\n\nexport async function GET() {\n  const { userId, orgId } = await auth();\n\n  if (!userId) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  \u002F\u002F User's personal projects or org projects\n  const projects = await prisma.project.findMany({\n    where: orgId\n      ? { organizationId: orgId }\n      : { userId, organizationId: null },\n  });\n\n  return NextResponse.json(projects);\n}\n\nexport async function POST(req: Request) {\n  const { userId, orgId } = await auth();\n\n  if (!userId) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  const body = await req.json();\n\n  const project = await prisma.project.create({\n    data: {\n      name: body.name,\n      userId,\n      organizationId: orgId ?? null,\n    },\n  });\n\n  return NextResponse.json(project, { status: 201 });\n}\n\n\u002F\u002F Protected with role check\n\u002F\u002F app\u002Fapi\u002Fadmin\u002Fusers\u002Froute.ts\nexport async function GET() {\n  const { userId, orgRole } = await auth();\n\n  if (!userId) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  if (orgRole !== 'org:admin') {\n    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });\n  }\n\n  \u002F\u002F Admin-only logic\n  const users = await prisma.user.findMany();\n  return NextResponse.json(users);\n}\n\n\u002F\u002F Using getAuth in older patterns (not recommended)\n\u002F\u002F For backwards compatibility only\nimport { getAuth } from '@clerk\u002Fnextjs\u002Fserver';\n\nexport async function GET(req: Request) {\n  const { userId } = getAuth(req);\n  \u002F\u002F ...\n}\n\n### Anti_patterns\n\n- Pattern: Trusting middleware alone | Why: Middleware can be bypassed (CVE-2025-29927) | Fix: Always verify auth in route handler too\n- Pattern: Not checking orgId for multi-tenant | Why: Users might access other org's data | Fix: Always filter by orgId from auth()\n\n### References\n\n- https:\u002F\u002Fclerk.com\u002Fdocs\u002Fguides\u002Fprotecting-pages\n\n## Sharp Edges\n\n### CVE-2025-29927 Middleware Bypass Vulnerability\n\nSeverity: CRITICAL\n\n### Multiple Middleware Files Cause Conflicts\n\nSeverity: HIGH\n\n### 4KB Session Token Cookie Limit\n\nSeverity: HIGH\n\n### auth() Requires clerkMiddleware Configuration\n\nSeverity: HIGH\n\n### Webhook Race Conditions\n\nSeverity: MEDIUM\n\n### auth() is Async in App Router\n\nSeverity: MEDIUM\n\n### Middleware Blocks Webhook Endpoints\n\nSeverity: MEDIUM\n\n### Accessing Auth State Before isLoaded\n\nSeverity: MEDIUM\n\n### Manual Redirects Cause Double Redirects\n\nSeverity: MEDIUM\n\n### Organization Data Not Scoped by orgId\n\nSeverity: HIGH\n\n## Validation Checks\n\n### Clerk Secret Key in Client Code\n\nSeverity: ERROR\n\nCLERK_SECRET_KEY must only be used server-side\n\nMessage: Clerk secret key exposed to client. Use CLERK_SECRET_KEY without NEXT_PUBLIC prefix.\n\n### Protected Route Without Middleware\n\nSeverity: ERROR\n\nAPI routes should have middleware protection\n\nMessage: API route without auth check. Add middleware protection or auth() check.\n\n### Hardcoded Clerk API Keys\n\nSeverity: ERROR\n\nClerk keys should use environment variables\n\nMessage: Hardcoded Clerk keys. Use environment variables.\n\n### Missing Await on auth()\n\nSeverity: ERROR\n\nauth() is async in App Router and must be awaited\n\nMessage: auth() not awaited. Use 'await auth()' in App Router.\n\n### Multiple Middleware Files\n\nSeverity: WARNING\n\nOnly one middleware.ts file should exist\n\nMessage: Multiple middleware files detected. Use single middleware.ts.\n\n### Webhook Route Not Excluded from Protection\n\nSeverity: WARNING\n\nWebhook routes should be public\n\nMessage: Webhook route may be blocked by middleware. Add to public routes.\n\n### Accessing Auth Without isLoaded Check\n\nSeverity: WARNING\n\nCheck isLoaded before accessing user state in client components\n\nMessage: Accessing user without isLoaded check. Check isLoaded first.\n\n### Clerk Hooks in Server Component\n\nSeverity: ERROR\n\nClerk hooks only work in Client Components\n\nMessage: Clerk hooks in Server Component. Add 'use client' or use auth().\n\n### Multi-Tenant Query Without orgId\n\nSeverity: WARNING\n\nOrganization data should be scoped by orgId\n\nMessage: Query without organization scope. Filter by orgId for multi-tenancy.\n\n### Webhook Without Signature Verification\n\nSeverity: ERROR\n\nClerk webhooks must verify svix signature\n\nMessage: Webhook without signature verification. Use svix to verify.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs database -> postgres-wizard (User table with clerkId)\n- user needs payments -> stripe-integration (Customer linked to Clerk user)\n- user needs search -> algolia-search (Secured API keys per user)\n- user needs analytics -> segment-cdp (User identification)\n- user needs email -> resend-email (Transactional emails)\n\n## When to Use\n- User mentions or implies: adding authentication\n- User mentions or implies: clerk auth\n- User mentions or implies: user authentication\n- User mentions or implies: sign in\n- User mentions or implies: sign up\n- User mentions or implies: user management\n- User mentions or implies: multi-tenancy\n- User mentions or implies: organizations\n- User mentions or implies: sso\n- User mentions or implies: single sign-on\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,122,620,"2026-05-16 13:11:10",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"2ea7ddc2-be08-4ca8-96ff-1d0a60f84836","1.0.0","clerk-auth.zip",6296,"uploads\u002Fskills\u002F7a2730a4-040a-41e9-bdeb-80bbcb2cef6d\u002Fclerk-auth.zip","5fb0e305aa21e2d6995ab610a1dda681bce51df79149cab432f06f17dc47de73","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":20862}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]