convex-clerk
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex + Clerk Authentication
Convex + Clerk 认证集成
Provider-specific patterns for integrating Clerk with Convex.
将Clerk与Convex集成的专属Provider模式。
Required Configuration
必要配置
1. auth.config.ts
1. auth.config.ts
typescript
// convex/auth.config.ts
import { AuthConfig } from 'convex/server';
export default {
providers: [
{
domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
applicationID: 'convex'
}
]
} satisfies AuthConfig;CRITICAL: JWT template in Clerk MUST be named exactly .
convextypescript
// convex/auth.config.ts
import { AuthConfig } from 'convex/server';
export default {
providers: [
{
domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
applicationID: 'convex'
}
]
} satisfies AuthConfig;重要提示: Clerk中的JWT模板必须精确命名为。
convex2. Environment Variables
2. 环境变量
bash
undefinedbash
undefined.env.local (Vite)
.env.local (Vite)
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
.env.local (Next.js)
.env.local (Next.js)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
Convex Dashboard Environment Variables
Convex控制台环境变量
CLERK_JWT_ISSUER_DOMAIN=https://verb-noun-00.clerk.accounts.dev
CLERK_WEBHOOK_SECRET=whsec_... # If using webhooks
undefinedCLERK_JWT_ISSUER_DOMAIN=https://verb-noun-00.clerk.accounts.dev
CLERK_WEBHOOK_SECRET=whsec_... # 如果使用webhook
undefinedClient Setup
客户端设置
React (Vite)
React (Vite)
typescript
// src/main.tsx
import { ClerkProvider, useAuth } from "@clerk/clerk-react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
ReactDOM.createRoot(document.getElementById("root")!).render(
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<App />
</ConvexProviderWithClerk>
</ClerkProvider>
);typescript
// src/main.tsx
import { ClerkProvider, useAuth } from "@clerk/clerk-react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
ReactDOM.createRoot(document.getElementById("root")!).render(
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<App />
</ConvexProviderWithClerk>
</ClerkProvider>
);Next.js App Router
Next.js App Router
typescript
// components/ConvexClientProvider.tsx
'use client';
import { ReactNode } from 'react';
import { ConvexReactClient } from 'convex/react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { useAuth } from '@clerk/nextjs';
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export default function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
);
}typescript
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
import ConvexClientProvider from '@/components/ConvexClientProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ClerkProvider>
<ConvexClientProvider>{children}</ConvexClientProvider>
</ClerkProvider>
</body>
</html>
);
}typescript
// components/ConvexClientProvider.tsx
'use client';
import { ReactNode } from 'react';
import { ConvexReactClient } from 'convex/react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { useAuth } from '@clerk/nextjs';
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export default function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
);
}typescript
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
import ConvexClientProvider from '@/components/ConvexClientProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ClerkProvider>
<ConvexClientProvider>{children}</ConvexClientProvider>
</ClerkProvider>
</body>
</html>
);
}Next.js Middleware
Next.js Middleware
typescript
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};typescript
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};UI Components
UI组件
Use Convex auth components, NOT Clerk's:
typescript
// ✅ Correct
import { Authenticated, Unauthenticated, AuthLoading } from 'convex/react';
// ❌ Don't use these for conditional rendering
import { SignedIn, SignedOut } from '@clerk/clerk-react';typescript
import { SignInButton, UserButton } from "@clerk/clerk-react";
import { Authenticated, Unauthenticated } from "convex/react";
function App() {
return (
<>
<Authenticated>
<UserButton />
<Content />
</Authenticated>
<Unauthenticated>
<SignInButton />
</Unauthenticated>
</>
);
}使用Convex的认证组件,而非Clerk的:
typescript
// ✅ 正确
import { Authenticated, Unauthenticated, AuthLoading } from 'convex/react';
// ❌ 条件渲染请勿使用这些
import { SignedIn, SignedOut } from '@clerk/clerk-react';typescript
import { SignInButton, UserButton } from "@clerk/clerk-react";
import { Authenticated, Unauthenticated } from "convex/react";
function App() {
return (
<>
<Authenticated>
<UserButton />
<Content />
</Authenticated>
<Unauthenticated>
<SignInButton />
</Unauthenticated>
</>
);
}Clerk Webhooks for User Sync
用于用户同步的Clerk Webhook
See WEBHOOKS.md for complete implementation.
Setup in Clerk Dashboard:
- Webhooks > Add Endpoint
- URL:
https://your-deployment.convex.site/clerk-users-webhook - Events: Select all events
user.* - Copy Signing Secret → Convex Dashboard env vars as
CLERK_WEBHOOK_SECRET
完整实现请参考WEBHOOKS.md。
在Clerk控制台中设置:
- Webhooks > 添加端点
- URL:
https://your-deployment.convex.site/clerk-users-webhook - 事件:选择所有事件
user.* - 复制签名密钥 → 在Convex控制台环境变量中设置为
CLERK_WEBHOOK_SECRET
Accessing User Info
获取用户信息
Client-side (Clerk SDK)
客户端(Clerk SDK)
typescript
import { useUser } from "@clerk/clerk-react";
function Profile() {
const { user } = useUser();
return <span>Hello, {user?.fullName}</span>;
}typescript
import { useUser } from "@clerk/clerk-react";
function Profile() {
const { user } = useUser();
return <span>你好,{user?.fullName}</span>;
}Server-side (Convex functions)
服务端(Convex函数)
typescript
export const myQuery = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
// identity.name, identity.email, etc.
// Fields depend on Clerk JWT template claims config
}
});typescript
export const myQuery = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
// identity.name, identity.email等
// 字段取决于Clerk JWT模板声明配置
}
});Dev vs Prod Configuration
开发与生产环境配置
| Environment | Publishable Key | Issuer Domain |
|---|---|---|
| Development | | |
| Production | | |
Set different values in Convex Dashboard for dev vs prod deployments.
| 环境 | 可发布密钥 | 颁发者域名 |
|---|---|---|
| 开发环境 | | |
| 生产环境 | | |
为开发和生产部署在Convex控制台中设置不同的值。
Clerk-Specific Troubleshooting
Clerk专属故障排查
| Issue | Cause | Fix |
|---|---|---|
| Token not generated | JWT template not named "convex" | Rename template to exactly |
| Wrong applicationID | Use |
| Wrong domain | Copy Frontend API URL from Clerk |
| Webhook fails | Wrong secret | Copy Signing Secret from Clerk webhook |
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 未生成Token | JWT模板未命名为"convex" | 将模板精确重命名为 |
| applicationID错误 | 使用 |
| 域名错误 | 从Clerk复制前端API URL |
| Webhook调用失败 | 密钥错误 | 从Clerk webhook复制签名密钥 |
DO ✅
✅ 正确做法
- Name JWT template exactly
convex - Use with
ConvexProviderWithClerkfrom ClerkuseAuth - Use not Clerk's
useConvexAuth()for auth stateuseAuth() - Use Convex's not Clerk's
<Authenticated><SignedIn> - Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard
- 将JWT模板精确命名为
convex - 使用并搭配Clerk的
ConvexProviderWithClerkuseAuth - 认证状态使用而非Clerk的
useConvexAuth()useAuth() - 使用Convex的而非Clerk的
<Authenticated><SignedIn> - 在Convex控制台中设置CLERK_JWT_ISSUER_DOMAIN
DON'T ❌
❌ 错误做法
- Rename the JWT template from "convex"
- Use Clerk's auth hooks to gate Convex queries
- Hardcode the issuer domain (use env var)
- Forget to deploy after changing auth.config.ts
- 将JWT模板从"convex"重命名
- 使用Clerk的auth钩子来限制Convex查询
- 硬编码颁发者域名(使用环境变量)
- 修改auth.config.ts后忘记部署