---
id: clerk-wire
name: clerk-wire
description: Wire Clerk authentication into a Next.js (App Router) project — middleware auth gate, sign-in/sign-up routes, UserButton, server helpers, and the user → DB sync webhook.
version: 1.0.0
source: bundled
tags: [auth, clerk, saas, nextjs]
---

# Skill: clerk-wire

Use this skill when the user asks to **add Clerk auth** to a Next.js App
Router project that doesn't already have it wired. Most often invoked
right after `openlife scaffold meu-saas --template=nextjs-supabase-clerk-stripe`
when the user wants to customize beyond the bundled defaults, or when
adding Clerk to an existing project.

## Pre-flight checks (run before mutating files)

1. Verify Next.js 14+ App Router project (look for `app/` directory, not `pages/`)
2. Confirm `@clerk/nextjs` is in `package.json` deps; if not, run `npm install @clerk/nextjs svix`
3. Check for existing `middleware.ts` — if present, ask user before overwriting
4. Verify `.env.local` has `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` placeholders

## Files to create / modify

### `middleware.ts` (root of project)

```ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher([
  '/(dashboard)(.*)',
  '/api/(?!webhooks)(.*)',
]);

export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) {
    await auth.protect();
  }
});

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)',
    '/(api|trpc)(.*)',
  ],
};
```

### `app/layout.tsx` — wrap with `<ClerkProvider>`

Wrap the root layout body with `<ClerkProvider>` (imported from `@clerk/nextjs`).

### `app/(auth)/sign-in/[[...sign-in]]/page.tsx`

```tsx
import { SignIn } from '@clerk/nextjs';
export default function Page() {
  return <div className="flex min-h-screen items-center justify-center"><SignIn /></div>;
}
```

### `app/(auth)/sign-up/[[...sign-up]]/page.tsx`

Same shape, swap `SignIn` for `SignUp`.

### `lib/auth.ts` — server helpers

```ts
import { auth } from '@clerk/nextjs/server';
import { redirect } from 'next/navigation';

export async function requireUserId(): Promise<string> {
  const { userId } = await auth();
  if (!userId) redirect('/sign-in');
  return userId;
}

export async function getUserId(): Promise<string | null> {
  const { userId } = await auth();
  return userId;
}
```

### `app/api/webhooks/clerk/route.ts` — user sync to DB

See the bundled `nextjs-supabase-clerk-stripe` template for the complete
svix-verified webhook handler. Pattern:

1. Read `CLERK_WEBHOOK_SECRET` from env
2. Verify svix signature via `new Webhook(secret).verify(payload, headers)`
3. On `user.created` / `user.updated` → upsert into `users` table
4. On `user.deleted` → delete row

## Required environment

```bash
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...
```

## Post-install steps to surface to the user

1. Get keys from https://dashboard.clerk.com → Your App → API Keys
2. Configure webhook endpoint in Clerk Dashboard → Webhooks → Add Endpoint
   - URL: `https://<your-domain>/api/webhooks/clerk`
   - Events: `user.created`, `user.updated`, `user.deleted`
3. The webhook URL **must be publicly reachable** for production (Vercel/Railway/etc.)
4. Test locally via `ngrok http 3000` + temporary webhook endpoint in Clerk

## Composition with other skills

- `supabase-rls-wire` — pairs naturally; the Clerk webhook needs a `users` table with `clerk_id` column
- `stripe-billing-wire` — uses `getOrCreateStripeCustomerId(clerkUserId)` pattern
- `vercel-deploy-wire` — register env vars before deploy

## Verification

After applying:

```bash
npm run dev                  # http://localhost:3000
# Visit /sign-up → create user
# Visit /dashboard → should be authenticated
# Check `users` table → row should appear after webhook fires
```
