convex-clerk

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex + 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
convex
.
typescript
// 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模板必须精确命名为
convex

2. Environment Variables

2. 环境变量

bash
undefined
bash
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
undefined
CLERK_JWT_ISSUER_DOMAIN=https://verb-noun-00.clerk.accounts.dev CLERK_WEBHOOK_SECRET=whsec_... # 如果使用webhook
undefined

Client 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:
  1. Webhooks > Add Endpoint
  2. URL:
    https://your-deployment.convex.site/clerk-users-webhook
  3. Events: Select all
    user.*
    events
  4. Copy Signing Secret → Convex Dashboard env vars as
    CLERK_WEBHOOK_SECRET
完整实现请参考WEBHOOKS.md
在Clerk控制台中设置:
  1. Webhooks > 添加端点
  2. URL:
    https://your-deployment.convex.site/clerk-users-webhook
  3. 事件:选择所有
    user.*
    事件
  4. 复制签名密钥 → 在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

开发与生产环境配置

EnvironmentPublishable KeyIssuer Domain
Development
pk_test_...
https://verb-noun-00.clerk.accounts.dev
Production
pk_live_...
https://clerk.your-domain.com
Set different values in Convex Dashboard for dev vs prod deployments.
环境可发布密钥颁发者域名
开发环境
pk_test_...
https://verb-noun-00.clerk.accounts.dev
生产环境
pk_live_...
https://clerk.your-domain.com
为开发和生产部署在Convex控制台中设置不同的值。

Clerk-Specific Troubleshooting

Clerk专属故障排查

IssueCauseFix
Token not generatedJWT template not named "convex"Rename template to exactly
convex
aud
mismatch
Wrong applicationIDUse
applicationID: "convex"
iss
mismatch
Wrong domainCopy Frontend API URL from Clerk
Webhook failsWrong secretCopy Signing Secret from Clerk webhook
问题原因解决方法
未生成TokenJWT模板未命名为"convex"将模板精确重命名为
convex
aud
不匹配
applicationID错误使用
applicationID: "convex"
iss
不匹配
域名错误从Clerk复制前端API URL
Webhook调用失败密钥错误从Clerk webhook复制签名密钥

DO ✅

✅ 正确做法

  • Name JWT template exactly
    convex
  • Use
    ConvexProviderWithClerk
    with
    useAuth
    from Clerk
  • Use
    useConvexAuth()
    not Clerk's
    useAuth()
    for auth state
  • Use Convex's
    <Authenticated>
    not Clerk's
    <SignedIn>
  • Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard
  • 将JWT模板精确命名为
    convex
  • 使用
    ConvexProviderWithClerk
    并搭配Clerk的
    useAuth
  • 认证状态使用
    useConvexAuth()
    而非Clerk的
    useAuth()
  • 使用Convex的
    <Authenticated>
    而非Clerk的
    <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后忘记部署