TanStack Start
Install and configure Convex + Better Auth for TanStack Start.
Check out a complete Convex + Better Auth example with TanStack Start in the GitHub repo.
Installation
Install packages
Install the component, a pinned version of Better Auth, and ensure the latest version of Convex.
1.25.0
or later.npm install better-auth@1.3.8 --save-exact
npm install convex@latest @convex-dev/better-auth
Register the component
Register the Better Auth component in your Convex project.
import { defineApp } from "convex/server";
import betterAuth from "@convex-dev/better-auth/convex.config";
const app = defineApp();
app.use(betterAuth);
export default app;
Add Convex auth config
Add a convex/auth.config.ts
file to configure Better Auth as an authentication provider.
export default {
providers: [
{
domain: process.env.CONVEX_SITE_URL,
applicationID: "convex",
},
],
};
Set environment variables
Generate a secret for encryption and generating hashes. Use the command below if you have openssl installed, or use the button to generate a random value instead. Or generate your own however you like.
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
Add your site URL to your Convex deployment.
npx convex env set SITE_URL http://localhost:3000
Add environment variables to the .env.local
file created by npx convex dev
. It will be picked up by your framework dev server.
# Deployment used by \`npx convex dev\`
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # team: team-name, project: project-name
VITE_CONVEX_URL=https://adjective-animal-123.convex.cloud
# Same as VITE_CONVEX_URL but ends in .site
VITE_CONVEX_SITE_URL=https://adjective-animal-123.convex.site
# Your local site URL
SITE_URL=http://localhost:3000
Create a Better Auth instance
Create a Better Auth instance and initialize the component.
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
import { betterAuth } from "better-auth";
const siteUrl = process.env.SITE_URL!;
// The component client has methods needed for integrating Convex with Better Auth,
// as well as helper methods for general use.
export const authComponent = createClient<DataModel>(components.betterAuth);
export const createAuth = (
ctx: GenericCtx<DataModel>,
{ optionsOnly } = { optionsOnly: false },
) => {
return betterAuth({
// disable logging when createAuth is called just to generate options.
// this is not required, but there's a lot of noise in logs without it.
logger: {
disabled: optionsOnly,
},
baseURL: siteUrl,
database: authComponent.adapter(ctx),
// Configure simple, non-verified email/password to get started
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
plugins: [
// The Convex plugin is required for Convex compatibility
convex(),
],
});
};
// Example function for getting the current user
// Feel free to edit, omit, etc.
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
return authComponent.getAuthUser(ctx);
},
});
Create a Better Auth client instance
Create a Better Auth client instance for interacting with the Better Auth server from your client.
import { createAuthClient } from "better-auth/react";
import { convexClient } from "@convex-dev/better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [convexClient()],
});
Mount handlers
Register Better Auth route handlers on your Convex deployment.
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./auth";
const http = httpRouter();
authComponent.registerRoutes(http, createAuth);
export default http;
Set up route handlers to proxy auth requests from TanStack Start to your Convex deployment.
import { reactStartHandler } from '@convex-dev/better-auth/react-start'
import { createServerFileRoute } from '@tanstack/react-start/server'
export const ServerRoute = createServerFileRoute('/api/auth/$').methods({
GET: ({ request }) => {
return reactStartHandler(request)
},
POST: ({ request }) => {
return reactStartHandler(request)
},
})
Set up root route
Wrap your application root with ConvexBetterAuthProvider
and make auth available in loaders.
import * as React from 'react'
import {
Outlet,
createRootRouteWithContext,
useRouteContext,
} from '@tanstack/react-router'
import {
Meta,
Scripts,
createServerFn,
} from '@tanstack/react-start'
import { QueryClient } from '@tanstack/react-query'
import appCss from '@/styles/app.css?url'
import { ConvexQueryClient } from '@convex-dev/react-query'
import { ConvexReactClient } from 'convex/react'
import { getCookie, getWebRequest } from '@tanstack/react-start/server'
import { ConvexBetterAuthProvider } from '@convex-dev/better-auth/react'
import { fetchSession, getCookieName } from '@convex-dev/better-auth/react-start'
import { authClient } from "@/lib/auth-client";
// Get auth information for SSR using available cookies
const fetchAuth = createServerFn({ method: 'GET' }).handler(async () => {
const { createAuth } = await import('../../convex/auth')
const { session } = await fetchSession(getWebRequest())
const sessionCookieName = getCookieName(createAuth)
const token = getCookie(sessionCookieName)
return {
userId: session?.user.id,
token,
}
})
export const Route = createRootRouteWithContext<{
queryClient: QueryClient
convexClient: ConvexReactClient
convexQueryClient: ConvexQueryClient
}>()({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
],
links: [
{ rel: 'stylesheet', href: appCss },
{ rel: 'icon', href: '/favicon.ico' },
],
}),
beforeLoad: async (ctx) => {
// all queries, mutations and action made with TanStack Query will be
// authenticated by an identity token.
const { userId, token } = await fetchAuth()
// During SSR only (the only time serverHttpClient exists),
// set the auth token to make HTTP queries with.
if (token) {
ctx.context.convexQueryClient.serverHttpClient?.setAuth(token)
}
return { userId, token }
},
component: RootComponent,
})
function RootComponent() {
const context = useRouteContext({ from: Route.id })
return (
<ConvexBetterAuthProvider
client={context.convexClient}
authClient={authClient}
>
<RootDocument>
<Outlet />
</RootDocument>
</ConvexBetterAuthProvider>
)
}
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className="dark">
<head>
<Meta />
</head>
<body className="bg-neutral-950 text-neutral-50">
{children}
<Scripts />
</body>
</html>
)
}
Add route context
Provide context from Convex to your routes.
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
import { routerWithQueryClient } from '@tanstack/react-router-with-query'
import { ConvexProvider, ConvexReactClient } from 'convex/react'
import { ConvexQueryClient } from '@convex-dev/react-query'
import { QueryClient } from '@tanstack/react-query'
export function createRouter() {
const CONVEX_URL = (import.meta as any).env.VITE_CONVEX_URL!
if (!CONVEX_URL) {
throw new Error('missing VITE_CONVEX_URL envar')
}
const convex = new ConvexReactClient(CONVEX_URL, {
unsavedChangesWarning: false,
})
const convexQueryClient = new ConvexQueryClient(convex)
const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
})
convexQueryClient.connect(queryClient)
const router = routerWithQueryClient(
createTanStackRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
context: { queryClient, convexClient: convex, convexQueryClient },
Wrap: ({ children }) => (
<ConvexProvider client={convexQueryClient.convexClient}>
{children}
</ConvexProvider>
),
}),
queryClient,
)
return router
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
You're done!
You're now ready to start using Better Auth with Convex.
Usage
Using Better Auth from the server
To use Better Auth's server methods in server rendering, server functions, or any other TanStack Start server code, use Convex functions and call the function from your server code.
First, export helpers for calling Convex functions from your server code.
import { createAuth } from "convex/auth";
import { setupFetchClient } from "@convex-dev/better-auth/react-start";
export const { fetchQuery, fetchMutation, fetchAction } =
setupFetchClient(createAuth);
Here's an example Convex function that uses Better Auth's server methods, and a TanStack server function that calls the Convex function.
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { createAuth, authComponent } from "./auth";
export const updateUserPassword = mutation({
args: {
currentPassword: v.string(),
newPassword: v.string(),
},
handler: async (ctx, args) => {
await createAuth(ctx).api.changePassword({
body: {
currentPassword: args.currentPassword,
newPassword: args.newPassword,
},
headers: await authComponent.getHeaders(ctx),
});
},
});
import { createServerFn } from "@tanstack/react-start";
import { fetchMutation } from "@/lib/auth-server";
import { api } from "../../convex/_generated/api";
export const updatePassword = createServerFn({ method: "POST" }).handler(
async ({ data: { currentPassword, newPassword } }) => {
await fetchMutation(api.users.updatePassword, {
currentPassword,
newPassword,
});
}
);