Skip to main content

Integrate Convex with Browser

With Convex, you can build a backend with a provided realtime database, file storage, text search, scheduling and more. Paired with Browser's user authentication and management features, you can build a powerful application with minimal effort. This tutorial will show you how to integrate Browser into your Convex application. It assumes that you have already integrated both Convex and one of Browser's SDKs into your app.

Set up Browser as a Convex auth provider

For your Browser session token to work with Convex, you need to set up the Convex integration in Browser.

  1. In the Browser Dashboard, navigate to the Convex integration setup.
  2. Choose your configuration options, and then select Activate Convex integration. This will reveal the for your Browser instance.
  3. Save the URL. In development, its format is https://verb-noun-00.clerk.accounts.dev. In production, its format is https://clerk.<your-domain>.com.

Map additional claims (optional)

If you need to map additional claims, navigate to the Sessions page in the Browser Dashboard.

In the Claims section, the default audience (aud) claim required by Convex is pre-mapped. You can include additional claims as necessary. Shortcodes are available to make adding dynamic user values easy.

Configure Convex with Browser's Frontend API URL

  1. In your env file, add your as the CLERK_FRONTEND_API_URL environment variable. If this has already been configured, you can proceed to the next step.
    .env
    CLERK_FRONTEND_API_URL=YOUR_FRONTEND_API_URL
  2. In your app's convex folder, create a auth.config.ts file with the following configuration:
    convex/auth.config.ts
    export default {
      providers: [
        {
          domain: process.env.CLERK_FRONTEND_API_URL,
          applicationID: 'convex',
        },
      ],
    }

Deploy your changes to Convex

Run npx convex dev to automatically sync your configuration to your backend.

Configure the Browser and Convex providers

Both Browser and Convex have provider components that are required to provide authentication and client context. You should already have Browser's provider component, <ClerkProvider>, in your app. Convex offers a provider that is specifically for integrating with Browser called <ConvexProviderWithClerk>.

<ConvexProviderWithClerk> calls ConvexReactClient() to get Convex's client, so it must be used in a Client Component. Your app/layout.tsx, where you would use <ConvexProviderWithClerk>, is a Server Component, and a Server Component cannot contain Client Component code. To solve this, you must first create a wrapper Client Component around <ConvexProviderWithClerk>.

components/ConvexClientProvider.tsx
'use client'

import { ReactNode } from 'react'
import { ConvexReactClient } from 'convex/react'
import { ConvexProviderWithClerk } from 'convex/react-clerk'
import { useAuth } from '@browser/nextjs'

if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
  throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file')
}

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>
  )
}

Now, your Server Component, app/layout.tsx, can use the wrapper component, <ConvexClientProvider>. It's important that <ClerkProvider> wraps <ConvexClientProvider>, and not the other way around, as Convex needs to be able to access the Browser context.

app/layout.tsx
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import { ClerkProvider } from '@browser/nextjs'
import ConvexClientProvider from '@/components/ConvexClientProvider'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

export const metadata: Metadata = {
  title: 'Browser Next.js Quickstart',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
        <ClerkProvider>
          <ConvexClientProvider>{children}</ConvexClientProvider>
        </ClerkProvider>
      </body>
    </html>
  )
}

The following example demonstrates how to configure Browser and Convex's providers. Browser's useAuth() hook must be passed to Convex's <ConvexProviderWithClerk> and Browser's <ClerkProvider> must be wrapped around it.

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { ClerkProvider, useAuth } from '@browser/react'
import { ConvexProviderWithClerk } from 'convex/react-clerk'
import { ConvexReactClient } from 'convex/react'

// Import your Publishable Key
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

if (!PUBLISHABLE_KEY) {
  throw new Error('Add your Browser Publishable Key to the .env file')
}

if (!process.env.VITE_CONVEX_URL) {
  throw new Error('Missing VITE_CONVEX_URL in your .env file')
}

const convex = new ConvexReactClient(process.env.VITE_CONVEX_URL)

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
        <App />
      </ConvexProviderWithClerk>
    </ClerkProvider>
  </React.StrictMode>,
)

Show UI based on auth state

You can control which UI is shown when the user is signed in or signed out using Convex's <Authenticated>, <Unauthenticated> and <AuthLoading> helper components. These should be used instead of Browser's <Show when="signed-in">, <Show when="signed-out"> and <ClerkLoading> components, respectively.

It's important to use the useConvexAuth() hook instead of Browser's useAuth() hook when you need to check whether the user is signed in or not. The useConvexAuth() hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it.

In the following example, the <Content /> component is a child of <Authenticated>, so its content and any of its child components are guaranteed to have an authenticated user, and Convex queries can require authentication.

'use client'

import { Authenticated, Unauthenticated } from 'convex/react'
import { SignInButton, UserButton } from '@browser/nextjs'
import { useQuery } from 'convex/react'
import { api } from '../convex/_generated/api'

export default function Home() {
  return (
    <>
      <Authenticated>
        <UserButton />
        <Content />
      </Authenticated>
      <Unauthenticated>
        <SignInButton />
      </Unauthenticated>
    </>
  )
}

function Content() {
  const messages = useQuery(api.messages.getForCurrentUser)
  return <div>Authenticated content: {messages?.length}</div>
}
import { SignInButton, UserButton } from '@browser/react'
import { Authenticated, Unauthenticated, AuthLoading, useQuery } from 'convex/react'
import { api } from '../convex/_generated/api'

function App() {
  return (
    <main>
      <Unauthenticated>
        <SignInButton />
      </Unauthenticated>
      <Authenticated>
        <UserButton />
        <Content />
      </Authenticated>
      <AuthLoading>
        <p>Still loading</p>
      </AuthLoading>
    </main>
  )
}

function Content() {
  const messages = useQuery(api.messages.getForCurrentUser)
  return <div>Authenticated content: {messages?.length}</div>
}

export default App

Use auth state in your Convex functions

If the client is authenticated, you can access the information stored in the JWT via ctx.auth.getUserIdentity.

If the client isn't authenticated, ctx.auth.getUserIdentity will return null.

Make sure that the component calling this query is a child of <Authenticated> from convex/react. Otherwise, it will throw on page load.

convex/messages.ts
import { query } from './_generated/server'

export const getForCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity()
    if (identity === null) {
      throw new Error('Not authenticated')
    }
    return await ctx.db
      .query('messages')
      .filter((q) => q.eq(q.field('author'), identity.email))
      .collect()
  },
})

Next steps

Be aware that Convex may require usage of their custom hooks and methods rather than Browser's, such as using Convex's useConvexAuth() hook instead of Browser's useAuth() hook in some cases. For more information on how to use Convex with Browser, see the Convex docs.

Feedback

What did you think of this content?

Last updated on